FL-system's Intelligent Cache - HAL - Revenir à l'accueil

 

 

Branding Farnell element14 (France)

 

Farnell Element 14 :

Miniature

Everything You Need To Know About Arduino

Miniature

Tutorial 01 for Arduino: Getting Acquainted with Arduino

Miniature

The Cube® 3D Printer

Miniature

What's easier- DIY Dentistry or our new our website features?

 

Miniature

Ben Heck's Getting Started with the BeagleBone Black Trailer

Miniature

Ben Heck's Home-Brew Solder Reflow Oven 2.0 Trailer

Miniature

Get Started with Pi Episode 3 - Online with Raspberry Pi

Miniature

Discover Simulink Promo -- Exclusive element14 Webinar

Miniature

Ben Heck's TV Proximity Sensor Trailer

Miniature

Ben Heck's PlayStation 4 Teardown Trailer

See the trailer for the next exciting episode of The Ben Heck show. Check back on Friday to be among the first to see the exclusive full show on element…

Miniature

Get Started with Pi Episode 4 - Your First Raspberry Pi Project

Connect your Raspberry Pi to a breadboard, download some code and create a push-button audio play project.

Miniature

Ben Heck Anti-Pickpocket Wallet Trailer

Miniature

Molex Earphones - The 14 Holiday Products of Newark element14 Promotion

Miniature

Tripp Lite Surge Protector - The 14 Holiday Products of Newark element14 Promotion

Miniature

Microchip ChipKIT Pi - The 14 Holiday Products of Newark element14 Promotion

Miniature

Beagle Bone Black - The 14 Holiday Products of Newark element14 Promotion

Miniature

3M E26, LED Lamps - The 14 Holiday Products of Newark element14 Promotion

Miniature

3M Colored Duct Tape - The 14 Holiday Products of Newark element14 Promotion

Miniature

Tenma Soldering Station - The 14 Holiday Products of Newark element14 Promotion

Miniature

Duratool Screwdriver Kit - The 14 Holiday Products of Newark element14 Promotion

Miniature

Cubify 3D Cube - The 14 Holiday Products of Newark element14 Promotion

Miniature

Bud Boardganizer - The 14 Holiday Products of Newark element14 Promotion

Miniature

Raspberry Pi Starter Kit - The 14 Holiday Products of Newark element14 Promotion

Miniature

Fluke 323 True-rms Clamp Meter - The 14 Holiday Products of Newark element14 Promotion

Miniature

Dymo RHINO 6000 Label Printer - The 14 Holiday Products of Newark element14 Promotion

Miniature

3M LED Advanced Lights A-19 - The 14 Holiday Products of Newark element14 Promotion

Miniature

Innovative LPS Resistor Features Very High Power Dissipation

Miniature

Charge Injection Evaluation Board for DG508B Multiplexer Demo

Miniature

Ben Heck The Great Glue Gun Trailer Part 2

Miniature

Introducing element14 TV

Miniature

Ben Heck Time to Meet Your Maker Trailer

Miniature

Détecteur de composants

Miniature

Recherche intégrée

Miniature

Ben Builds an Accessibility Guitar Trailer Part 1

Miniature

Ben Builds an Accessibility Guitar - Part 2 Trailer

Miniature

PiFace Control and Display Introduction

Miniature

Flashmob Farnell

Miniature

Express Yourself in 3D with Cube 3D Printers from Newark element14

Miniature

Farnell YouTube Channel Move

Miniature

Farnell: Design with the best

Miniature

French Farnell Quest

Miniature

Altera - 3 Ways to Quickly Adapt to Changing Ethernet Protocols

Miniature

Cy-Net3 Network Module

Miniature

MC AT - Professional and Precision Series Thin Film Chip Resistors

Miniature

Solderless LED Connector

Miniature

PSA-T Series Spectrum Analyser: PSA1301T/ PSA2701T

Miniature

3-axis Universal Motion Controller For Stepper Motor Drivers: TMC429

Miniature

Voltage Level Translation

Puce électronique / Microchip :

Miniature

Microchip - 8-bit Wireless Development Kit

Miniature

Microchip - Introduction to mTouch Capacitive Touch Sensing Part 2 of 3

Miniature

Microchip - Introduction to mTouch Capacitive Touch Sensing Part 3 of 3

Miniature

Microchip - Introduction to mTouch Capacitive Touch Sensing Part 1 of 3

Sans fil - Wireless :

Miniature

Microchip - 8-bit Wireless Development Kit

Miniature

Wireless Power Solutions - Wurth Electronics, Texas Instruments, CadSoft and element14

Miniature

Analog Devices - Remote Water Quality Monitoring via a Low Power, Wireless Network

Texas instrument :

Miniature

Texas Instruments - Automotive LED Headlights

Miniature

Texas Instruments - Digital Power Solutions

Miniature

Texas Instruments - Industrial Sensor Solutions

Miniature

Texas Instruments - Wireless Pen Input Demo (Mobile World Congress)

Miniature

Texas Instruments - Industrial Automation System Components

Miniature

Texas Instruments - TMS320C66x - Industry's first 10-GHz fixed/floating point DSP

Miniature

Texas Instruments - TMS320C66x KeyStone Multicore Architecture

Miniature

Texas Instruments - Industrial Interfaces

Miniature

Texas Instruments - Concerto™ MCUs - Connectivity without compromise

Miniature

Texas Instruments - Stellaris Robot Chronos

Miniature

Texas Instruments - DRV8412-C2-KIT, Brushed DC and Stepper Motor Control Kit

Ordinateurs :

Miniature

Ask Ben Heck - Connect Raspberry Pi to Car Computer

Miniature

Ben's Portable Raspberry Pi Computer Trailer

Miniature

Ben's Raspberry Pi Portable Computer Trailer 2

Miniature

Ben Heck's Pocket Computer Trailer

Miniature

Ask Ben Heck - Atari Computer

Miniature

Ask Ben Heck - Using Computer Monitors for External Displays

Miniature

Raspberry Pi Partnership with BBC Computer Literacy Project - Answers from co-founder Eben Upton

Miniature

Installing RaspBMC on your Raspberry Pi with the Farnell element14 Accessory kit

Miniature

Raspberry Pi Served - Joey Hudy

Miniature

Happy Birthday Raspberry Pi

Miniature

Raspberry Pi board B product overview

Logiciels :

Miniature

Ask Ben Heck - Best Opensource or Free CAD Software

Miniature

Tektronix FPGAView™ software makes debugging of FPGAs faster than ever!

Miniature

Ask Ben Heck - Best Open-Source Schematic Capture and PCB Layout Software

Miniature

Introduction to Cadsoft EAGLE PCB Design Software in Chinese

Miniature

Altera - Developing Software for Embedded Systems on FPGAs

Tutoriels :

Miniature

Ben Heck The Great Glue Gun Trailer Part 1

Miniature

the knode tutorial - element14

Miniature

Ben's Autodesk 123D Tutorial Trailer

Miniature

Ben's CadSoft EAGLE Tutorial Trailer

Miniature

Ben Heck's Soldering Tutorial Trailer

Miniature

Ben Heck's AVR Dev Board tutorial

Miniature

Ben Heck's Pinball Tutorial Trailer

Miniature

Ben Heck's Interface Tutorial Trailer

Miniature

First Stage with Python and PiFace Digital

Miniature

Cypress - Getting Started with PSoC® 3 - Part 2

Miniature

Energy Harvesting Challenge

Miniature

New Features of CadSoft EAGLE v6

Autres documentations : [TXT]
 Analog-Devices-Convo..> 09-Sep-2014 08:26  2.1M  
[TXT]
 Analog-Devices-Convo..> 09-Sep-2014 08:25  2.2M  
[TXT]
 Analog-Devices-Convo..> 09-Sep-2014 08:25  2.2M  
[TXT]
 Analog-Devices-ADMC4..> 09-Sep-2014 08:23  2.3M  
[TXT]
 Analog-Devices-Wi-Fi..> 09-Sep-2014 08:23  2.3M  
[TXT]
 Analog-Devices-ADMC2..> 09-Sep-2014 08:21  2.4M  
[TXT]
 Analog-Devices-ADC-S..> 09-Sep-2014 08:21  2.4M  
[TXT]
 Analog-Devices-Visua..> 09-Sep-2014 08:18  2.5M  
[TXT]
 Analog-Devices-ANF32..> 09-Sep-2014 08:18  2.6M  
[TXT]
 Farnell-Compensating..> 09-Sep-2014 08:16  2.6M  
[TXT]
 Farnell-Compensating..> 09-Sep-2014 08:16  2.6M  
[TXT]
 Farnell-LM7805-Fairc..> 09-Sep-2014 08:13  2.7M  
[TXT]
 Farnell-AD620-Rev-H-..> 09-Sep-2014 08:13  2.6M  
[TXT]
 Farnell-Datasheet-FT..> 09-Sep-2014 08:10  2.8M  
[TXT]
 Farnell-MAX4661-MAX4..> 09-Sep-2014 08:10  2.8M  
[TXT]
 Farnell-OPA627-Texas..> 09-Sep-2014 08:08  2.8M  
[TXT]
 Farnell-REF19x-Serie..> 09-Sep-2014 08:08  2.8M  
[TXT]
 Farnell-Data-Sheet-M..> 09-Sep-2014 08:05  2.8M  
[TXT]
 Farnell-ULN2803A-Rev..> 09-Sep-2014 08:05  2.9M  
[TXT]
 Analog-Devices-Digit..> 08-Sep-2014 18:03  2.0M  
[TXT]
 Analog-Devices-Digit..> 08-Sep-2014 18:02  2.1M  
[TXT]
 Analog-Devices-Basic..> 08-Sep-2014 17:49  1.9M  
[TXT]
 Farnell-AD9833-Rev-E..> 08-Sep-2014 17:49  1.8M  
[TXT]
 Farnell-The-Discrete..> 08-Sep-2014 17:44  1.8M  
[TXT]
 Electronique-Basic-o..> 08-Sep-2014 17:43  1.8M  
[TXT]
 Analog-Devices-AN300..> 08-Sep-2014 17:42  2.0M  
[TXT]
 Analog-Devices-The-C..> 08-Sep-2014 17:41  1.9M  
[TXT]
 Analog-Devices-Intro..> 08-Sep-2014 17:39  1.9M  
[TXT]
 Analog-Devices-Compl..> 08-Sep-2014 17:38  2.0M  
[TXT]
 Analog-Devices-Gloss..> 08-Sep-2014 17:36  2.0M  
[TXT]
 Farnell-ADuM1300-ADu..> 08-Sep-2014 08:11  1.7M  
[TXT]
 Farnell-AD586BRZ-Ana..> 08-Sep-2014 08:09  1.6M  
[TXT]
 Farnell-ADuM1200-ADu..> 08-Sep-2014 08:09  1.6M  
[TXT]
 Farnell-NA555-NE555-..> 08-Sep-2014 07:51  1.5M  
[TXT]
 Farnell-AD9834-Rev-D..> 08-Sep-2014 07:32  1.2M  
[TXT]
 Farnell-MSP430F15x-M..> 08-Sep-2014 07:32  1.3M  
[TXT]
 Farnell-AD736-Rev-I-..> 08-Sep-2014 07:31  1.3M  
[TXT]
 Farnell-AD8307-Data-..> 08-Sep-2014 07:30  1.3M  
[TXT]
 Farnell-Single-Chip-..> 08-Sep-2014 07:30  1.5M  
[TXT]
 Farnell-Quadruple-2-..> 08-Sep-2014 07:29  1.5M  
[TXT]
 Farnell-ADE7758-Rev-..> 08-Sep-2014 07:28  1.7M  
[TXT]
 Farnell-MAX3221-Rev-..> 08-Sep-2014 07:28  1.8M  
[TXT]
 Farnell-USB-to-Seria..> 08-Sep-2014 07:27  2.0M  
[TXT]
 Farnell-AD8313-Analo..> 08-Sep-2014 07:26  2.0M  
[TXT]
 Farnell-SN54HC164-SN..> 08-Sep-2014 07:25  2.0M  
[TXT]
 Farnell-AD8310-Analo..> 08-Sep-2014 07:24  2.1M  
[TXT]
 Farnell-AD8361-Rev-D..> 08-Sep-2014 07:23  2.1M  
[TXT]
 Farnell-2N3906-Fairc..> 08-Sep-2014 07:22  2.1M  
[TXT]
 Farnell-AD584-Rev-C-..> 08-Sep-2014 07:20  2.2M  
[TXT]
 Farnell-ADE7753-Rev-..> 08-Sep-2014 07:20  2.3M  
[TXT]
 Farnell-TLV320AIC23B..> 08-Sep-2014 07:18  2.4M 

[TXT]

 Farnell-NA555-NE555-..> 08-Sep-2014 07:33  1.5M  

[TXT]

 Farnell-AD9834-Rev-D..> 08-Sep-2014 07:32  1.2M  

[TXT]

 Farnell-MSP430F15x-M..> 08-Sep-2014 07:32  1.3M  

[TXT]

 Farnell-AD736-Rev-I-..> 08-Sep-2014 07:31  1.3M  

[TXT]

 Farnell-AD8307-Data-..> 08-Sep-2014 07:30  1.3M  

[TXT]

 Farnell-Single-Chip-..> 08-Sep-2014 07:30  1.5M  

[TXT]

 Farnell-Quadruple-2-..> 08-Sep-2014 07:29  1.5M  

[TXT]

 Farnell-ADE7758-Rev-..> 08-Sep-2014 07:28  1.7M  

[TXT]

 Farnell-MAX3221-Rev-..> 08-Sep-2014 07:28  1.8M  

[TXT]

 Farnell-USB-to-Seria..> 08-Sep-2014 07:27  2.0M  

[TXT]

 Farnell-AD8313-Analo..> 08-Sep-2014 07:26  2.0M  

[TXT]

 Farnell-SN54HC164-SN..> 08-Sep-2014 07:25  2.0M  

[TXT]

 Farnell-AD8310-Analo..> 08-Sep-2014 07:24  2.1M  

[TXT]

 Farnell-AD8361-Rev-D..> 08-Sep-2014 07:23  2.1M  

[TXT]

 Farnell-2N3906-Fairc..> 08-Sep-2014 07:22  2.1M  

[TXT]

 Farnell-AD584-Rev-C-..> 08-Sep-2014 07:20  2.2M  

[TXT]

 Farnell-ADE7753-Rev-..> 08-Sep-2014 07:20  2.3M  

[TXT]

 Farnell-TLV320AIC23B..> 08-Sep-2014 07:18  2.4M  

[TXT]

 Farnell-AD586BRZ-Ana..> 08-Sep-2014 07:17  1.6M  

[TXT]

 Farnell-STM32F405xxS..> 27-Aug-2014 18:27  1.8M 
 Farnell-MSP430-Hardw..> 29-Jul-2014 10:36  1.1M  

[TXT]

 Farnell-LM324-Texas-..> 29-Jul-2014 10:32  1.5M  

[TXT]

 Farnell-LM386-Low-Vo..> 29-Jul-2014 10:32  1.5M  

[TXT]

 Farnell-NE5532-Texas..> 29-Jul-2014 10:32  1.5M  

[TXT]

 Farnell-Hex-Inverter..> 29-Jul-2014 10:31  875K  

[TXT]

 Farnell-AT90USBKey-H..> 29-Jul-2014 10:31  902K  

[TXT]

 Farnell-AT89C5131-Ha..> 29-Jul-2014 10:31  1.2M  

[TXT]

 Farnell-MSP-EXP430F5..> 29-Jul-2014 10:31  1.2M  

[TXT]

 Farnell-Explorer-16-..> 29-Jul-2014 10:31  1.3M  

[TXT]

 Farnell-TMP006EVM-Us..> 29-Jul-2014 10:30  1.3M  

[TXT]

 Farnell-Gertboard-Us..> 29-Jul-2014 10:30  1.4M  

[TXT]

 Farnell-LMP91051-Use..> 29-Jul-2014 10:30  1.4M  

[TXT]

 Farnell-Thermometre-..> 29-Jul-2014 10:30  1.4M  

[TXT]

 Farnell-user-manuel-..> 29-Jul-2014 10:29  1.5M  

[TXT]

 Farnell-fx-3650P-fx-..> 29-Jul-2014 10:29  1.5M

[TXT]

 Farnell-2-GBPS-Diffe..> 28-Jul-2014 17:42  2.7M  

[TXT]

 Farnell-LMT88-2.4V-1..> 28-Jul-2014 17:42  2.8M  

[TXT]

 Farnell-Octal-Genera..> 28-Jul-2014 17:42  2.8M  

[TXT]

 Farnell-Dual-MOSFET-..> 28-Jul-2014 17:41  2.8M  

[TXT]

 Farnell-TLV320AIC325..> 28-Jul-2014 17:41  2.9M  

[TXT]

 Farnell-SN54LV4053A-..> 28-Jul-2014 17:20  5.9M  

[TXT]

 Farnell-TAS1020B-USB..> 28-Jul-2014 17:19  6.2M  

[TXT]

 Farnell-TPS40060-Wid..> 28-Jul-2014 17:19  6.3M  

[TXT]

 Farnell-TL082-Wide-B..> 28-Jul-2014 17:16  6.3M  

[TXT]

 Farnell-RF-short-tra..> 28-Jul-2014 17:16  6.3M  

[TXT]

 Farnell-maxim-integr..> 28-Jul-2014 17:14  6.4M  

[TXT]

 Farnell-TSV6390-TSV6..> 28-Jul-2014 17:14  6.4M  

[TXT]

 Farnell-Fast-Charge-..> 28-Jul-2014 17:12  6.4M  

[TXT]

 Farnell-NVE-datashee..> 28-Jul-2014 17:12  6.5M  

[TXT]

 Farnell-Excalibur-Hi..> 28-Jul-2014 17:10  2.4M  

[TXT]

 Farnell-Excalibur-Hi..> 28-Jul-2014 17:10  2.4M  

[TXT]

 Farnell-REF102-10V-P..> 28-Jul-2014 17:09  2.4M  

[TXT]

 Farnell-TMS320F28055..> 28-Jul-2014 17:09  2.7M

[TXT]

 Farnell-MULTICOMP-Ra..> 22-Jul-2014 12:35  5.9M  

[TXT]

 Farnell-RASPBERRY-PI..> 22-Jul-2014 12:35  5.9M  

[TXT]

 Farnell-Dremel-Exper..> 22-Jul-2014 12:34  1.6M  

[TXT]

 Farnell-STM32F103x8-..> 22-Jul-2014 12:33  1.6M  

[TXT]

 Farnell-BD6xxx-PDF.htm  22-Jul-2014 12:33  1.6M  

[TXT]

 Farnell-L78S-STMicro..> 22-Jul-2014 12:32  1.6M  

[TXT]

 Farnell-RaspiCam-Doc..> 22-Jul-2014 12:32  1.6M  

[TXT]

 Farnell-SB520-SB5100..> 22-Jul-2014 12:32  1.6M  

[TXT]

 Farnell-iServer-Micr..> 22-Jul-2014 12:32  1.6M  

[TXT]

 Farnell-LUMINARY-MIC..> 22-Jul-2014 12:31  3.6M  

[TXT]

 Farnell-TEXAS-INSTRU..> 22-Jul-2014 12:31  2.4M  

[TXT]

 Farnell-TEXAS-INSTRU..> 22-Jul-2014 12:30  4.6M  

[TXT]

 Farnell-CLASS 1-or-2..> 22-Jul-2014 12:30  4.7M  

[TXT]

 Farnell-TEXAS-INSTRU..> 22-Jul-2014 12:29  4.8M  

[TXT]

 Farnell-Evaluating-t..> 22-Jul-2014 12:28  4.9M  

[TXT]

 Farnell-LM3S6952-Mic..> 22-Jul-2014 12:27  5.9M  

[TXT]

 Farnell-Keyboard-Mou..> 22-Jul-2014 12:27  5.9M 

 [TXT] Farnell-Full-Datashe..> 15-Jul-2014 17:08 951K  

[TXT]

 Farnell-pmbta13_pmbt..> 15-Jul-2014 17:06  959K  

[TXT]

 Farnell-EE-SPX303N-4..> 15-Jul-2014 17:06  969K  

[TXT]

 Farnell-Datasheet-NX..> 15-Jul-2014 17:06  1.0M  

[TXT]

 Farnell-Datasheet-Fa..> 15-Jul-2014 17:05  1.0M  

[TXT]

 Farnell-MIDAS-un-tra..> 15-Jul-2014 17:05  1.0M  

[TXT]

 Farnell-SERIAL-TFT-M..> 15-Jul-2014 17:05  1.0M  

[TXT]

 Farnell-MCOC1-Farnel..> 15-Jul-2014 17:05  1.0M

[TXT]

 Farnell-TMR-2-series..> 15-Jul-2014 16:48  787K  

[TXT]

 Farnell-DC-DC-Conver..> 15-Jul-2014 16:48  781K  

[TXT]

 Farnell-Full-Datashe..> 15-Jul-2014 16:47  803K  

[TXT]

 Farnell-TMLM-Series-..> 15-Jul-2014 16:47  810K  

[TXT]

 Farnell-TEL-5-Series..> 15-Jul-2014 16:47  814K  

[TXT]

 Farnell-TXL-series-t..> 15-Jul-2014 16:47  829K  

[TXT]

 Farnell-TEP-150WI-Se..> 15-Jul-2014 16:47  837K  

[TXT]

 Farnell-AC-DC-Power-..> 15-Jul-2014 16:47  845K  

[TXT]

 Farnell-TIS-Instruct..> 15-Jul-2014 16:47  845K  

[TXT]

 Farnell-TOS-tracopow..> 15-Jul-2014 16:47  852K  

[TXT]

 Farnell-TCL-DC-traco..> 15-Jul-2014 16:46  858K  

[TXT]

 Farnell-TIS-series-t..> 15-Jul-2014 16:46  875K  

[TXT]

 Farnell-TMR-2-Series..> 15-Jul-2014 16:46  897K  

[TXT]

 Farnell-TMR-3-WI-Ser..> 15-Jul-2014 16:46  939K  

[TXT]

 Farnell-TEN-8-WI-Ser..> 15-Jul-2014 16:46  939K  

[TXT]

 Farnell-Full-Datashe..> 15-Jul-2014 16:46  947K
[TXT]

 Farnell-HIP4081A-Int..> 07-Jul-2014 19:47  1.0M  

[TXT]

 Farnell-ISL6251-ISL6..> 07-Jul-2014 19:47  1.1M  

[TXT]

 Farnell-DG411-DG412-..> 07-Jul-2014 19:47  1.0M  

[TXT]

 Farnell-3367-ARALDIT..> 07-Jul-2014 19:46  1.2M  

[TXT]

 Farnell-ICM7228-Inte..> 07-Jul-2014 19:46  1.1M  

[TXT]

 Farnell-Data-Sheet-K..> 07-Jul-2014 19:46  1.2M  

[TXT]

 Farnell-Silica-Gel-M..> 07-Jul-2014 19:46  1.2M  

[TXT]

 Farnell-TKC2-Dusters..> 07-Jul-2014 19:46  1.2M  

[TXT]

 Farnell-CRC-HANDCLEA..> 07-Jul-2014 19:46  1.2M  

[TXT]

 Farnell-760G-French-..> 07-Jul-2014 19:45  1.2M  

[TXT]

 Farnell-Decapant-KF-..> 07-Jul-2014 19:45  1.2M  

[TXT]

 Farnell-1734-ARALDIT..> 07-Jul-2014 19:45  1.2M  

[TXT]

 Farnell-Araldite-Fus..> 07-Jul-2014 19:45  1.2M  

[TXT]

 Farnell-fiche-de-don..> 07-Jul-2014 19:44  1.4M  

[TXT]

 Farnell-safety-data-..> 07-Jul-2014 19:44  1.4M  

[TXT]

 Farnell-A-4-Hardener..> 07-Jul-2014 19:44  1.4M  

[TXT]

 Farnell-CC-Debugger-..> 07-Jul-2014 19:44  1.5M  

[TXT]

 Farnell-MSP430-Hardw..> 07-Jul-2014 19:43  1.8M  

[TXT]

 Farnell-SmartRF06-Ev..> 07-Jul-2014 19:43  1.6M  

[TXT]

 Farnell-CC2531-USB-H..> 07-Jul-2014 19:43  1.8M  

[TXT]

 Farnell-Alimentation..> 07-Jul-2014 19:43  1.8M  

[TXT]

 Farnell-BK889B-PONT-..> 07-Jul-2014 19:42  1.8M  

[TXT]

 Farnell-User-Guide-M..> 07-Jul-2014 19:41  2.0M  

[TXT]

 Farnell-T672-3000-Se..> 07-Jul-2014 19:41  2.0M

 [TXT]Farnell-0050375063-D..> 18-Jul-2014 17:03 2.5M  

[TXT]

 Farnell-Mini-Fit-Jr-..> 18-Jul-2014 17:03  2.5M  

[TXT]

 Farnell-43031-0002-M..> 18-Jul-2014 17:03  2.5M  

[TXT]

 Farnell-0433751001-D..> 18-Jul-2014 17:02  2.5M  

[TXT]

 Farnell-Cube-3D-Prin..> 18-Jul-2014 17:02  2.5M  

[TXT]

 Farnell-MTX-Compact-..> 18-Jul-2014 17:01  2.5M  

[TXT]

 Farnell-MTX-3250-MTX..> 18-Jul-2014 17:01  2.5M  

[TXT]

 Farnell-ATtiny26-L-A..> 18-Jul-2014 17:00  2.6M  

[TXT]

 Farnell-MCP3421-Micr..> 18-Jul-2014 17:00  1.2M  

[TXT]

 Farnell-LM19-Texas-I..> 18-Jul-2014 17:00  1.2M  

[TXT]

 Farnell-Data-Sheet-S..> 18-Jul-2014 17:00  1.2M  

[TXT]

 Farnell-LMH6518-Texa..> 18-Jul-2014 16:59  1.3M  

[TXT]

 Farnell-AD7719-Low-V..> 18-Jul-2014 16:59  1.4M  

[TXT]

 Farnell-DAC8143-Data..> 18-Jul-2014 16:59  1.5M  

[TXT]

 Farnell-BGA7124-400-..> 18-Jul-2014 16:59  1.5M  

[TXT]

 Farnell-SICK-OPTIC-E..> 18-Jul-2014 16:58  1.5M  

[TXT]

 Farnell-LT3757-Linea..> 18-Jul-2014 16:58  1.6M  

[TXT]

 Farnell-LT1961-Linea..> 18-Jul-2014 16:58  1.6M  

[TXT]

 Farnell-PIC18F2420-2..> 18-Jul-2014 16:57  2.5M  

[TXT]

 Farnell-DS3231-DS-PD..> 18-Jul-2014 16:57  2.5M  

[TXT]

 Farnell-RDS-80-PDF.htm  18-Jul-2014 16:57  1.3M  

[TXT]

 Farnell-AD8300-Data-..> 18-Jul-2014 16:56  1.3M  

[TXT]

 Farnell-LT6233-Linea..> 18-Jul-2014 16:56  1.3M  

[TXT]

 Farnell-MAX1365-MAX1..> 18-Jul-2014 16:56  1.4M  

[TXT]

 Farnell-XPSAF5130-PD..> 18-Jul-2014 16:56  1.4M  

[TXT]

 Farnell-DP83846A-DsP..> 18-Jul-2014 16:55  1.5M  

[TXT]

 Farnell-Dremel-Exper..> 18-Jul-2014 16:55  1.6M

[TXT]

 Farnell-MCOC1-Farnel..> 16-Jul-2014 09:04  1.0M  

[TXT]

 Farnell-SL3S1203_121..> 16-Jul-2014 09:04  1.1M  

[TXT]

 Farnell-PN512-Full-N..> 16-Jul-2014 09:03  1.4M  

[TXT]

 Farnell-SL3S4011_402..> 16-Jul-2014 09:03  1.1M  

[TXT]

 Farnell-LPC408x-7x 3..> 16-Jul-2014 09:03  1.6M  

[TXT]

 Farnell-PCF8574-PCF8..> 16-Jul-2014 09:03  1.7M  

[TXT]

 Farnell-LPC81xM-32-b..> 16-Jul-2014 09:02  2.0M  

[TXT]

 Farnell-LPC1769-68-6..> 16-Jul-2014 09:02  1.9M  

[TXT]

 Farnell-Download-dat..> 16-Jul-2014 09:02  2.2M  

[TXT]

 Farnell-LPC3220-30-4..> 16-Jul-2014 09:02  2.2M  

[TXT]

 Farnell-LPC11U3x-32-..> 16-Jul-2014 09:01  2.4M  

[TXT]

 Farnell-SL3ICS1002-1..> 16-Jul-2014 09:01  2.5M

[TXT]

 Farnell-T672-3000-Se..> 08-Jul-2014 18:59  2.0M  

[TXT]

 Farnell-tesa®pack63..> 08-Jul-2014 18:56  2.0M  

[TXT]

 Farnell-Encodeur-USB..> 08-Jul-2014 18:56  2.0M  

[TXT]

 Farnell-CC2530ZDK-Us..> 08-Jul-2014 18:55  2.1M  

[TXT]

 Farnell-2020-Manuel-..> 08-Jul-2014 18:55  2.1M  

[TXT]

 Farnell-Synchronous-..> 08-Jul-2014 18:54  2.1M  

[TXT]

 Farnell-Arithmetic-L..> 08-Jul-2014 18:54  2.1M  

[TXT]

 Farnell-NA555-NE555-..> 08-Jul-2014 18:53  2.2M  

[TXT]

 Farnell-4-Bit-Magnit..> 08-Jul-2014 18:53  2.2M  

[TXT]

 Farnell-LM555-Timer-..> 08-Jul-2014 18:53  2.2M  

[TXT]

 Farnell-L293d-Texas-..> 08-Jul-2014 18:53  2.2M  

[TXT]

 Farnell-SN54HC244-SN..> 08-Jul-2014 18:52  2.3M  

[TXT]

 Farnell-MAX232-MAX23..> 08-Jul-2014 18:52  2.3M  

[TXT]

 Farnell-High-precisi..> 08-Jul-2014 18:51  2.3M  

[TXT]

 Farnell-SMU-Instrume..> 08-Jul-2014 18:51  2.3M  

[TXT]

 Farnell-900-Series-B..> 08-Jul-2014 18:50  2.3M  

[TXT]

 Farnell-BA-Series-Oh..> 08-Jul-2014 18:50  2.3M  

[TXT]

 Farnell-UTS-Series-S..> 08-Jul-2014 18:49  2.5M  

[TXT]

 Farnell-270-Series-O..> 08-Jul-2014 18:49  2.3M  

[TXT]

 Farnell-UTS-Series-S..> 08-Jul-2014 18:49  2.8M  

[TXT]

 Farnell-Tiva-C-Serie..> 08-Jul-2014 18:49  2.6M  

[TXT]

 Farnell-UTO-Souriau-..> 08-Jul-2014 18:48  2.8M  

[TXT]

 Farnell-Clipper-Seri..> 08-Jul-2014 18:48  2.8M  

[TXT]

 Farnell-SOURIAU-Cont..> 08-Jul-2014 18:47  3.0M  

[TXT]

 Farnell-851-Series-P..> 08-Jul-2014 18:47  3.0M

 [TXT] Farnell-SL59830-Inte..> 06-Jul-2014 10:07 1.0M  

[TXT]

 Farnell-ALF1210-PDF.htm 06-Jul-2014 10:06  4.0M  

[TXT]

 Farnell-AD7171-16-Bi..> 06-Jul-2014 10:06  1.0M  

[TXT]

 Farnell-Low-Noise-24..> 06-Jul-2014 10:05  1.0M  

[TXT]

 Farnell-ESCON-Featur..> 06-Jul-2014 10:05  938K  

[TXT]

 Farnell-74LCX573-Fai..> 06-Jul-2014 10:05  1.9M  

[TXT]

 Farnell-1N4148WS-Fai..> 06-Jul-2014 10:04  1.9M  

[TXT]

 Farnell-FAN6756-Fair..> 06-Jul-2014 10:04  850K  

[TXT]

 Farnell-Datasheet-Fa..> 06-Jul-2014 10:04  861K  

[TXT]

 Farnell-ES1F-ES1J-fi..> 06-Jul-2014 10:04  867K  

[TXT]

 Farnell-QRE1113-Fair..> 06-Jul-2014 10:03  879K  

[TXT]

 Farnell-2N7002DW-Fai..> 06-Jul-2014 10:03  886K  

[TXT]

 Farnell-FDC2512-Fair..> 06-Jul-2014 10:03  886K  

[TXT]

 Farnell-FDV301N-Digi..> 06-Jul-2014 10:03  886K  

[TXT]

 Farnell-S1A-Fairchil..> 06-Jul-2014 10:03  896K  

[TXT]

 Farnell-BAV99-Fairch..> 06-Jul-2014 10:03  896K  

[TXT]

 Farnell-74AC00-74ACT..> 06-Jul-2014 10:03  911K  

[TXT]

 Farnell-NaPiOn-Panas..> 06-Jul-2014 10:02  911K  

[TXT]

 Farnell-LQ-RELAYS-AL..> 06-Jul-2014 10:02  924K  

[TXT]

 Farnell-ev-relays-ae..> 06-Jul-2014 10:02  926K  

[TXT]

 Farnell-ESCON-Featur..> 06-Jul-2014 10:02  931K  

[TXT]

 Farnell-Amplifier-In..> 06-Jul-2014 10:02  940K  

[TXT]

 Farnell-Serial-File-..> 06-Jul-2014 10:02  941K  

[TXT]

 Farnell-Both-the-Del..> 06-Jul-2014 10:01  948K  

[TXT]

 Farnell-Videk-PDF.htm   06-Jul-2014 10:01  948K  

[TXT]

 Farnell-EPCOS-173438..> 04-Jul-2014 10:43  3.3M  

[TXT]

 Farnell-Sensorless-C..> 04-Jul-2014 10:42  3.3M  

[TXT]

 Farnell-197.31-KB-Te..> 04-Jul-2014 10:42  3.3M  

[TXT]

 Farnell-PIC12F609-61..> 04-Jul-2014 10:41  3.7M  

[TXT]

 Farnell-PADO-semi-au..> 04-Jul-2014 10:41  3.7M  

[TXT]

 Farnell-03-iec-runds..> 04-Jul-2014 10:40  3.7M  

[TXT]

 Farnell-ACC-Silicone..> 04-Jul-2014 10:40  3.7M  

[TXT]

 Farnell-Series-TDS10..> 04-Jul-2014 10:39  4.0M 

[TXT]

 Farnell-03-iec-runds..> 04-Jul-2014 10:40  3.7M  

[TXT]

 Farnell-0430300011-D..> 14-Jun-2014 18:13  2.0M  

[TXT]

 Farnell-06-6544-8-PD..> 26-Mar-2014 17:56  2.7M  

[TXT]

 Farnell-3M-Polyimide..> 21-Mar-2014 08:09  3.9M  

[TXT]

 Farnell-3M-VolitionT..> 25-Mar-2014 08:18  3.3M  

[TXT]

 Farnell-10BQ060-PDF.htm 14-Jun-2014 09:50  2.4M  

[TXT]

 Farnell-10TPB47M-End..> 14-Jun-2014 18:16  3.4M  

[TXT]

 Farnell-12mm-Size-In..> 14-Jun-2014 09:50  2.4M  

[TXT]

 Farnell-24AA024-24LC..> 23-Jun-2014 10:26  3.1M  

[TXT]

 Farnell-50A-High-Pow..> 20-Mar-2014 17:31  2.9M  

[TXT]

 Farnell-197.31-KB-Te..> 04-Jul-2014 10:42  3.3M  

[TXT]

 Farnell-1907-2006-PD..> 26-Mar-2014 17:56  2.7M  

[TXT]

 Farnell-5910-PDF.htm    25-Mar-2014 08:15  3.0M  

[TXT]

 Farnell-6517b-Electr..> 29-Mar-2014 11:12  3.3M  

[TXT]

 Farnell-A-True-Syste..> 29-Mar-2014 11:13  3.3M  

[TXT]

 Farnell-ACC-Silicone..> 04-Jul-2014 10:40  3.7M  

[TXT]

 Farnell-AD524-PDF.htm   20-Mar-2014 17:33  2.8M  

[TXT]

 Farnell-ADL6507-PDF.htm 14-Jun-2014 18:19  3.4M  

[TXT]

 Farnell-ADSP-21362-A..> 20-Mar-2014 17:34  2.8M  

[TXT]

 Farnell-ALF1210-PDF.htm 04-Jul-2014 10:39  4.0M  

[TXT]

 Farnell-ALF1225-12-V..> 01-Apr-2014 07:40  3.4M  

[TXT]

 Farnell-ALF2412-24-V..> 01-Apr-2014 07:39  3.4M  

[TXT]

 Farnell-AN10361-Phil..> 23-Jun-2014 10:29  2.1M  

[TXT]

 Farnell-ARADUR-HY-13..> 26-Mar-2014 17:55  2.8M  

[TXT]

 Farnell-ARALDITE-201..> 21-Mar-2014 08:12  3.7M  

[TXT]

 Farnell-ARALDITE-CW-..> 26-Mar-2014 17:56  2.7M  

[TXT]

 Farnell-ATMEL-8-bit-..> 19-Mar-2014 18:04  2.1M  

[TXT]

 Farnell-ATMEL-8-bit-..> 11-Mar-2014 07:55  2.1M  

[TXT]

 Farnell-ATmega640-VA..> 14-Jun-2014 09:49  2.5M  

[TXT]

 Farnell-ATtiny20-PDF..> 25-Mar-2014 08:19  3.6M  

[TXT]

 Farnell-ATtiny26-L-A..> 13-Jun-2014 18:40  1.8M  

[TXT]

 Farnell-Alimentation..> 14-Jun-2014 18:24  2.5M  

[TXT]

 Farnell-Alimentation..> 01-Apr-2014 07:42  3.4M  

[TXT]

 Farnell-Amplificateu..> 29-Mar-2014 11:11  3.3M  

[TXT]

 Farnell-An-Improved-..> 14-Jun-2014 09:49  2.5M  

[TXT]

 Farnell-Atmel-ATmega..> 19-Mar-2014 18:03  2.2M  

[TXT]

 Farnell-Avvertenze-e..> 14-Jun-2014 18:20  3.3M  

[TXT]

 Farnell-BC846DS-NXP-..> 13-Jun-2014 18:42  1.6M  

[TXT]

 Farnell-BC847DS-NXP-..> 23-Jun-2014 10:24  3.3M  

[TXT]

 Farnell-BF545A-BF545..> 23-Jun-2014 10:28  2.1M  

[TXT]

 Farnell-BK2650A-BK26..> 29-Mar-2014 11:10  3.3M  

[TXT]

 Farnell-BT151-650R-N..> 13-Jun-2014 18:40  1.7M  

[TXT]

 Farnell-BTA204-800C-..> 13-Jun-2014 18:42  1.6M  

[TXT]

 Farnell-BUJD203AX-NX..> 13-Jun-2014 18:41  1.7M  

[TXT]

 Farnell-BYV29F-600-N..> 13-Jun-2014 18:42  1.6M  

[TXT]

 Farnell-BYV79E-serie..> 10-Mar-2014 16:19  1.6M  

[TXT]

 Farnell-BZX384-serie..> 23-Jun-2014 10:29  2.1M  

[TXT]

 Farnell-Battery-GBA-..> 14-Jun-2014 18:13  2.0M  

[TXT]

 Farnell-C.A-6150-C.A..> 14-Jun-2014 18:24  2.5M  

[TXT]

 Farnell-C.A 8332B-C...> 01-Apr-2014 07:40  3.4M  

[TXT]

 Farnell-CC2560-Bluet..> 29-Mar-2014 11:14  2.8M  

[TXT]

 Farnell-CD4536B-Type..> 14-Jun-2014 18:13  2.0M  

[TXT]

 Farnell-CIRRUS-LOGIC..> 10-Mar-2014 17:20  2.1M  

[TXT]

 Farnell-CS5532-34-BS..> 01-Apr-2014 07:39  3.5M  

[TXT]

 Farnell-Cannon-ZD-PD..> 11-Mar-2014 08:13  2.8M  

[TXT]

 Farnell-Ceramic-tran..> 14-Jun-2014 18:19  3.4M  

[TXT]

 Farnell-Circuit-Note..> 26-Mar-2014 18:00  2.8M  

[TXT]

 Farnell-Circuit-Note..> 26-Mar-2014 18:00  2.8M  

[TXT]

 Farnell-Cles-electro..> 21-Mar-2014 08:13  3.9M  

[TXT]

 Farnell-Conception-d..> 11-Mar-2014 07:49  2.4M  

[TXT]

 Farnell-Connectors-N..> 14-Jun-2014 18:12  2.1M  

[TXT]

 Farnell-Construction..> 14-Jun-2014 18:25  2.5M  

[TXT]

 Farnell-Controle-de-..> 11-Mar-2014 08:16  2.8M  

[TXT]

 Farnell-Cordless-dri..> 14-Jun-2014 18:13  2.0M  

[TXT]

 Farnell-Current-Tran..> 26-Mar-2014 17:58  2.7M  

[TXT]

 Farnell-Current-Tran..> 26-Mar-2014 17:58  2.7M  

[TXT]

 Farnell-Current-Tran..> 26-Mar-2014 17:59  2.7M  

[TXT]

 Farnell-Current-Tran..> 26-Mar-2014 17:59  2.7M  

[TXT]

 Farnell-DC-Fan-type-..> 14-Jun-2014 09:48  2.5M  

[TXT]

 Farnell-DC-Fan-type-..> 14-Jun-2014 09:51  1.8M  

[TXT]

 Farnell-Davum-TMC-PD..> 14-Jun-2014 18:27  2.4M  

[TXT]

 Farnell-De-la-puissa..> 29-Mar-2014 11:10  3.3M  

[TXT]

 Farnell-Directive-re..> 25-Mar-2014 08:16  3.0M  

[TXT]

 Farnell-Documentatio..> 14-Jun-2014 18:26  2.5M  

[TXT]

 Farnell-Download-dat..> 13-Jun-2014 18:40  1.8M  

[TXT]

 Farnell-ECO-Series-T..> 20-Mar-2014 08:14  2.5M  

[TXT]

 Farnell-ELMA-PDF.htm    29-Mar-2014 11:13  3.3M  

[TXT]

 Farnell-EMC1182-PDF.htm 25-Mar-2014 08:17  3.0M  

[TXT]

 Farnell-EPCOS-173438..> 04-Jul-2014 10:43  3.3M  

[TXT]

 Farnell-EPCOS-Sample..> 11-Mar-2014 07:53  2.2M  

[TXT]

 Farnell-ES2333-PDF.htm  11-Mar-2014 08:14  2.8M  

[TXT]

 Farnell-Ed.081002-DA..> 19-Mar-2014 18:02  2.5M  

[TXT]

 Farnell-F28069-Picco..> 14-Jun-2014 18:14  2.0M  

[TXT]

 Farnell-F42202-PDF.htm  19-Mar-2014 18:00  2.5M  

[TXT]

 Farnell-FDS-ITW-Spra..> 14-Jun-2014 18:22  3.3M  

[TXT]

 Farnell-FICHE-DE-DON..> 10-Mar-2014 16:17  1.6M  

[TXT]

 Farnell-Fastrack-Sup..> 23-Jun-2014 10:25  3.3M  

[TXT]

 Farnell-Ferric-Chlor..> 29-Mar-2014 11:14  2.8M  

[TXT]

 Farnell-Fiche-de-don..> 14-Jun-2014 09:47  2.5M  

[TXT]

 Farnell-Fiche-de-don..> 14-Jun-2014 18:26  2.5M  

[TXT]

 Farnell-Fluke-1730-E..> 14-Jun-2014 18:23  2.5M  

[TXT]

 Farnell-GALVA-A-FROI..> 26-Mar-2014 17:56  2.7M  

[TXT]

 Farnell-GALVA-MAT-Re..> 26-Mar-2014 17:57  2.7M  

[TXT]

 Farnell-GN-RELAYS-AG..> 20-Mar-2014 08:11  2.6M  

[TXT]

 Farnell-HC49-4H-Crys..> 14-Jun-2014 18:20  3.3M  

[TXT]

 Farnell-HFE1600-Data..> 14-Jun-2014 18:22  3.3M  

[TXT]

 Farnell-HI-70300-Sol..> 14-Jun-2014 18:27  2.4M  

[TXT]

 Farnell-HUNTSMAN-Adv..> 10-Mar-2014 16:17  1.7M  

[TXT]

 Farnell-Haute-vitess..> 11-Mar-2014 08:17  2.4M  

[TXT]

 Farnell-IP4252CZ16-8..> 13-Jun-2014 18:41  1.7M  

[TXT]

 Farnell-Instructions..> 19-Mar-2014 18:01  2.5M  

[TXT]

 Farnell-KSZ8851SNL-S..> 23-Jun-2014 10:28  2.1M  

[TXT]

 Farnell-L-efficacite..> 11-Mar-2014 07:52  2.3M  

[TXT]

 Farnell-LCW-CQ7P.CC-..> 25-Mar-2014 08:19  3.2M  

[TXT]

 Farnell-LME49725-Pow..> 14-Jun-2014 09:49  2.5M  

[TXT]

 Farnell-LOCTITE-542-..> 25-Mar-2014 08:15  3.0M  

[TXT]

 Farnell-LOCTITE-3463..> 25-Mar-2014 08:19  3.0M  

[TXT]

 Farnell-LUXEON-Guide..> 11-Mar-2014 07:52  2.3M  

[TXT]

 Farnell-Leaded-Trans..> 23-Jun-2014 10:26  3.2M  

[TXT]

 Farnell-Les-derniers..> 11-Mar-2014 07:50  2.3M  

[TXT]

 Farnell-Loctite3455-..> 25-Mar-2014 08:16  3.0M  

[TXT]

 Farnell-Low-cost-Enc..> 13-Jun-2014 18:42  1.7M  

[TXT]

 Farnell-Lubrifiant-a..> 26-Mar-2014 18:00  2.7M  

[TXT]

 Farnell-MC3510-PDF.htm  25-Mar-2014 08:17  3.0M  

[TXT]

 Farnell-MC21605-PDF.htm 11-Mar-2014 08:14  2.8M  

[TXT]

 Farnell-MCF532x-7x-E..> 29-Mar-2014 11:14  2.8M  

[TXT]

 Farnell-MICREL-KSZ88..> 11-Mar-2014 07:54  2.2M  

[TXT]

 Farnell-MICROCHIP-PI..> 19-Mar-2014 18:02  2.5M  

[TXT]

 Farnell-MOLEX-39-00-..> 10-Mar-2014 17:19  1.9M  

[TXT]

 Farnell-MOLEX-43020-..> 10-Mar-2014 17:21  1.9M  

[TXT]

 Farnell-MOLEX-43160-..> 10-Mar-2014 17:21  1.9M  

[TXT]

 Farnell-MOLEX-87439-..> 10-Mar-2014 17:21  1.9M  

[TXT]

 Farnell-MPXV7002-Rev..> 20-Mar-2014 17:33  2.8M  

[TXT]

 Farnell-MX670-MX675-..> 14-Jun-2014 09:46  2.5M  

[TXT]

 Farnell-Microchip-MC..> 13-Jun-2014 18:27  1.8M  

[TXT]

 Farnell-Microship-PI..> 11-Mar-2014 07:53  2.2M  

[TXT]

 Farnell-Midas-Active..> 14-Jun-2014 18:17  3.4M  

[TXT]

 Farnell-Midas-MCCOG4..> 14-Jun-2014 18:11  2.1M  

[TXT]

 Farnell-Miniature-Ci..> 26-Mar-2014 17:55  2.8M  

[TXT]

 Farnell-Mistral-PDF.htm 14-Jun-2014 18:12  2.1M  

[TXT]

 Farnell-Molex-83421-..> 14-Jun-2014 18:17  3.4M  

[TXT]

 Farnell-Molex-COMMER..> 14-Jun-2014 18:16  3.4M  

[TXT]

 Farnell-Molex-Crimp-..> 10-Mar-2014 16:27  1.7M  

[TXT]

 Farnell-Multi-Functi..> 20-Mar-2014 17:38  3.0M  

[TXT]

 Farnell-NTE_SEMICOND..> 11-Mar-2014 07:52  2.3M  

[TXT]

 Farnell-NXP-74VHC126..> 10-Mar-2014 16:17  1.6M  

[TXT]

 Farnell-NXP-BT136-60..> 11-Mar-2014 07:52  2.3M  

[TXT]

 Farnell-NXP-PBSS9110..> 10-Mar-2014 17:21  1.9M  

[TXT]

 Farnell-NXP-PCA9555 ..> 11-Mar-2014 07:54  2.2M  

[TXT]

 Farnell-NXP-PMBFJ620..> 10-Mar-2014 16:16  1.7M  

[TXT]

 Farnell-NXP-PSMN1R7-..> 10-Mar-2014 16:17  1.6M  

[TXT]

 Farnell-NXP-PSMN7R0-..> 10-Mar-2014 17:19  2.1M  

[TXT]

 Farnell-NXP-TEA1703T..> 11-Mar-2014 08:15  2.8M  

[TXT]

 Farnell-Nilfi-sk-E-..> 14-Jun-2014 09:47  2.5M  

[TXT]

 Farnell-Novembre-201..> 20-Mar-2014 17:38  3.3M  

[TXT]

 Farnell-OMRON-Master..> 10-Mar-2014 16:26  1.8M  

[TXT]

 Farnell-OSLON-SSL-Ce..> 19-Mar-2014 18:03  2.1M  

[TXT]

 Farnell-OXPCIE958-FB..> 13-Jun-2014 18:40  1.8M  

[TXT]

 Farnell-PADO-semi-au..> 04-Jul-2014 10:41  3.7M  

[TXT]

 Farnell-PBSS5160T-60..> 19-Mar-2014 18:03  2.1M  

[TXT]

 Farnell-PDTA143X-ser..> 20-Mar-2014 08:12  2.6M  

[TXT]

 Farnell-PDTB123TT-NX..> 13-Jun-2014 18:43  1.5M  

[TXT]

 Farnell-PESD5V0F1BL-..> 13-Jun-2014 18:43  1.5M  

[TXT]

 Farnell-PESD9X5.0L-P..> 13-Jun-2014 18:43  1.6M  

[TXT]

 Farnell-PIC12F609-61..> 04-Jul-2014 10:41  3.7M  

[TXT]

 Farnell-PIC18F2455-2..> 23-Jun-2014 10:27  3.1M  

[TXT]

 Farnell-PIC24FJ256GB..> 14-Jun-2014 09:51  2.4M  

[TXT]

 Farnell-PMBT3906-PNP..> 13-Jun-2014 18:44  1.5M  

[TXT]

 Farnell-PMBT4403-PNP..> 23-Jun-2014 10:27  3.1M  

[TXT]

 Farnell-PMEG4002EL-N..> 14-Jun-2014 18:18  3.4M  

[TXT]

 Farnell-PMEG4010CEH-..> 13-Jun-2014 18:43  1.6M  

[TXT]

 Farnell-Panasonic-15..> 23-Jun-2014 10:29  2.1M  

[TXT]

 Farnell-Panasonic-EC..> 20-Mar-2014 17:36  2.6M  

[TXT]

 Farnell-Panasonic-EZ..> 20-Mar-2014 08:10  2.6M  

[TXT]

 Farnell-Panasonic-Id..> 20-Mar-2014 17:35  2.6M  

[TXT]

 Farnell-Panasonic-Ne..> 20-Mar-2014 17:36  2.6M  

[TXT]

 Farnell-Panasonic-Ra..> 20-Mar-2014 17:37  2.6M  

[TXT]

 Farnell-Panasonic-TS..> 20-Mar-2014 08:12  2.6M  

[TXT]

 Farnell-Panasonic-Y3..> 20-Mar-2014 08:11  2.6M  

[TXT]

 Farnell-Pico-Spox-Wi..> 10-Mar-2014 16:16  1.7M  

[TXT]

 Farnell-Pompes-Charg..> 24-Apr-2014 20:23  3.3M  

[TXT]

 Farnell-Ponts-RLC-po..> 14-Jun-2014 18:23  3.3M  

[TXT]

 Farnell-Portable-Ana..> 29-Mar-2014 11:16  2.8M  

[TXT]

 Farnell-Premier-Farn..> 21-Mar-2014 08:11  3.8M  

[TXT]

 Farnell-Produit-3430..> 14-Jun-2014 09:48  2.5M  

[TXT]

 Farnell-Proskit-SS-3..> 10-Mar-2014 16:26  1.8M  

[TXT]

 Farnell-Puissance-ut..> 11-Mar-2014 07:49  2.4M  

[TXT]

 Farnell-Q48-PDF.htm     23-Jun-2014 10:29  2.1M  

[TXT]

 Farnell-Radial-Lead-..> 20-Mar-2014 08:12  2.6M  

[TXT]

 Farnell-Realiser-un-..> 11-Mar-2014 07:51  2.3M  

[TXT]

 Farnell-Reglement-RE..> 21-Mar-2014 08:08  3.9M  

[TXT]

 Farnell-Repartiteurs..> 14-Jun-2014 18:26  2.5M  

[TXT]

 Farnell-S-TRI-SWT860..> 21-Mar-2014 08:11  3.8M  

[TXT]

 Farnell-SB175-Connec..> 11-Mar-2014 08:14  2.8M  

[TXT]

 Farnell-SMBJ-Transil..> 29-Mar-2014 11:12  3.3M  

[TXT]

 Farnell-SOT-23-Multi..> 11-Mar-2014 07:51  2.3M  

[TXT]

 Farnell-SPLC780A1-16..> 14-Jun-2014 18:25  2.5M  

[TXT]

 Farnell-SSC7102-Micr..> 23-Jun-2014 10:25  3.2M  

[TXT]

 Farnell-SVPE-series-..> 14-Jun-2014 18:15  2.0M  

[TXT]

 Farnell-Sensorless-C..> 04-Jul-2014 10:42  3.3M  

[TXT]

 Farnell-Septembre-20..> 20-Mar-2014 17:46  3.7M  

[TXT]

 Farnell-Serie-PicoSc..> 19-Mar-2014 18:01  2.5M  

[TXT]

 Farnell-Serie-Standa..> 14-Jun-2014 18:23  3.3M  

[TXT]

 Farnell-Series-2600B..> 20-Mar-2014 17:30  3.0M  

[TXT]

 Farnell-Series-TDS10..> 04-Jul-2014 10:39  4.0M  

[TXT]

 Farnell-Signal-PCB-R..> 14-Jun-2014 18:11  2.1M  

[TXT]

 Farnell-Strangkuhlko..> 21-Mar-2014 08:09  3.9M  

[TXT]

 Farnell-Supercapacit..> 26-Mar-2014 17:57  2.7M  

[TXT]

 Farnell-TDK-Lambda-H..> 14-Jun-2014 18:21  3.3M  

[TXT]

 Farnell-TEKTRONIX-DP..> 10-Mar-2014 17:20  2.0M  

[TXT]

 Farnell-Tektronix-AC..> 13-Jun-2014 18:44  1.5M  

[TXT]

 Farnell-Telemetres-l..> 20-Mar-2014 17:46  3.7M  

[TXT]

 Farnell-Termometros-..> 14-Jun-2014 18:14  2.0M  

[TXT]

 Farnell-The-essentia..> 10-Mar-2014 16:27  1.7M  

[TXT]

 Farnell-U2270B-PDF.htm  14-Jun-2014 18:15  3.4M  

[TXT]

 Farnell-USB-Buccanee..> 14-Jun-2014 09:48  2.5M  

[TXT]

 Farnell-USB1T11A-PDF..> 19-Mar-2014 18:03  2.1M  

[TXT]

 Farnell-V4N-PDF.htm     14-Jun-2014 18:11  2.1M  

[TXT]

 Farnell-WetTantalum-..> 11-Mar-2014 08:14  2.8M  

[TXT]

 Farnell-XPS-AC-Octop..> 14-Jun-2014 18:11  2.1M  

[TXT]

 Farnell-XPS-MC16-XPS..> 11-Mar-2014 08:15  2.8M  

[TXT]

 Farnell-YAGEO-DATA-S..> 11-Mar-2014 08:13  2.8M  

[TXT]

 Farnell-ZigBee-ou-le..> 11-Mar-2014 07:50  2.4M  

[TXT]

 Farnell-celpac-SUL84..> 21-Mar-2014 08:11  3.8M  

[TXT]

 Farnell-china_rohs_o..> 21-Mar-2014 10:04  3.9M  

[TXT]

 Farnell-cree-Xlamp-X..> 20-Mar-2014 17:34  2.8M  

[TXT]

 Farnell-cree-Xlamp-X..> 20-Mar-2014 17:35  2.7M  

[TXT]

 Farnell-cree-Xlamp-X..> 20-Mar-2014 17:31  2.9M  

[TXT]

 Farnell-cree-Xlamp-m..> 20-Mar-2014 17:32  2.9M  

[TXT]

 Farnell-cree-Xlamp-m..> 20-Mar-2014 17:32  2.9M  

[TXT]

 Farnell-ir1150s_fr.p..> 29-Mar-2014 11:11  3.3M  

[TXT]

 Farnell-manual-bus-p..> 10-Mar-2014 16:29  1.9M  

[TXT]

 Farnell-propose-plus..> 11-Mar-2014 08:19  2.8M  

[TXT]

 Farnell-techfirst_se..> 21-Mar-2014 08:08  3.9M  

[TXT]

 Farnell-testo-205-20..> 20-Mar-2014 17:37  3.0M  

[TXT]

 Farnell-testo-470-Fo..> 20-Mar-2014 17:38  3.0M  

[TXT]

 Farnell-uC-OS-III-Br..> 10-Mar-2014 17:20  2.0M  

[TXT]

 Sefram-7866HD.pdf-PD..> 29-Mar-2014 11:46  472K  

[TXT]

 Sefram-CAT_ENREGISTR..> 29-Mar-2014 11:46  461K  

[TXT]

 Sefram-CAT_MESUREURS..> 29-Mar-2014 11:46  435K  

[TXT]

 Sefram-GUIDE_SIMPLIF..> 29-Mar-2014 11:46  481K  

[TXT]

 Sefram-GUIDE_SIMPLIF..> 29-Mar-2014 11:46  442K  

[TXT]

 Sefram-GUIDE_SIMPLIF..> 29-Mar-2014 11:46  422K  

[TXT]

 Sefram-SP270.pdf-PDF..> 29-Mar-2014 11:46  464K
FL-system’s Intelligent Cache Gurvan Le Guernic, Julien Perret IRISA, Campus universitaire de Beaulieu, Avenue du G´en´eral Leclerc, 35042 Rennes Cedex - France fGurvan.Le Guernic, Julien.Perretg@irisa.fr R´esum´e : Cet article pr´esente une application de techniques issues du g´enie logiciel `a la mod´elisation d’environnements virtuels. Ces derniers, d`es lors qu’ils sont complexes, constituent des masses de donn´ees particuli`erement volumineuses et, de ce fait, difficiles `a manipuler. De plus, les descriptions exhaustives de ces environnements, c’est-`a-dire explicites, sont peu ´evolutives. Pour r´esoudre ces probl`emes, il existe des m´ethodes bas´ees sur des syst`emes de r´e´ecriture permettant de d´ecrire de fac¸on g´en´erique les objets de l’environnement. Chaque objet est ainsi d´ecrit par un axiome qui est r´e´ecrit lors de la g´en´eration de l’environnement. Lors de cette phase, il est fr´equent de retrouver deux s´equences de r´e´ecriture aboutissant `a un mˆeme objet. Il est alors possible d’utiliser un m´ecanisme tel que le cache afin d’am´eliorer les performances en profitant des calculs d´ej`a effectu´es. Cependant, se contenter de mettre en correspondance les noms et param`etres des axiomes n’est pas suffisant pour ´etablir l’ensemble des r´e´ecritures identiques. En effet, les r´e´ecritures d’un mˆeme axiome avec des param`etres effectifs diff´erents peuvent aboutir au mˆeme objet. Le syst`eme propos´e dans cet article effectue une analyse dynamique des r´e´ecritures afin d’am´eliorer les performances du cache en d´etectant de tels cas. La premi`ere partie de cet article pr´esente le syst`eme de r´e´ecriture, la seconde l’analyse utilis´ee, et la derni`ere le m´ecanisme propos´e. Mots-cl´es : R´ealit´e virtuelle - Syst`emes de r´e´ecriture - 3D - Cache 3D - Calcul de d´ependances - Mod´elisation et informatique fondamentale 1 INTRODUCTION La gestion d’environnements virtuels 3D de grande taille comporte des contraintes propres. Elle n´ecessite le stockage de la description d’un grand nombre d’´el´ements montrant de fortes similitudes structurelles et apparaissant `a l’identique ou presque `a plusieurs endroits de la sc`ene (on pourra par exemple penser `a une rang´ee de bancs ou de lampadaires). La description exhaustive d’un environnement urbain dans un langage 3D (e.g. VRML) fige le mod`ele 3D dans un ´etat unique (ou du moins difficilement maintenable) et n´ecessite une grande capacit´e de stockage. Une solution consiste `a utiliser le m´ecanisme de r´e´ecriture appel´e FLsystem [Marvie, 2003, Marvie, 2005]. Ce syst`eme, bas´e sur les L-systems [Lindenmayer, 1968, Smith, 1984] et les grammaires de Chomsky [Prusinkiewicz, 2001], permet de d´ecrire la structure g´en´erale des principaux types d’´el´ements rencontr´es dans l’environnement urbain tels que bˆatiments et arbres. La r´e´ecriture d’un axiome initial `a l’aide de la grammaire associ´ee aux ´el´ements urbains d’un type donn´e permet la g´en´eration automatique d’une entit´e urbaine de ce type. Cela permet une description compacte et ´evolutive d’environnements urbains 3D de grande taille. Il est ainsi possible de construire de nombreuse entit´es diff´erentes d’un mˆeme type d’objet en ex´ecutant plusieurs r´e´ecritures de l’axiome initial avec des param`etres diff´erents. L’inconv´enient majeur d’une telle m´ethode est le coˆut, en temps de calcul, impliqu´e par la phase de r´e´ecriture. Cependant, le surcoˆut impos´e par cette phase peut ˆetre r´eduit. Dans le cas de la g´en´eration d’une ville de grande taille, il est probable qu’un mˆeme bˆatiment1 apparaisse deux fois en des lieux diff´erents de la sc`ene. Il est encore plus probable d’avoir de multiples occurrences d’une mˆeme fenˆetre. Il serait donc int´eressant de pouvoir r´ecup´erer le r´esultat de r´e´ecritures d´ej`a effectu ´ees. Il est bien sur possible d’ajuster la grammaire pour qu’elle r´eutilise des ´el´ements 3D. N´eanmoins, cette m´ethode pr´esente l’inconv´enient de compliquer la tache du r´edacteur des grammaires et de diminuer la g´en´ericit´e structurelle des r`egles de r´e´ecriture. Une autre solution consiste `a utiliser un cache. Chaque fois qu’un axiome `a r´e´ecrire correspond `a une clef du cache, l’objet 3D correspondant `a cette clef est directement r´eutilis´e. Dˆu `a la nature param´etrique des axiomes du FL-system, un syst`eme simple de reconnaissance des clefs du cache se r´ev`ele insuffisant. Par exemple, un axiome g´en´erateur de bˆatiments simples (bat) peut ˆetre d´efini `a l’aide de 3 param`etres : d pour la distance au point d’observation (valant 1 pour un bˆatiment tr`es ´eloign´e), n pour le nombre d’´etages, et s pour le style du bˆatiment. Pour n constant, il est probable que cet axiome g´en`ere le mˆeme bˆatiment quel que soit s lorsque d vaut 1. Ainsi bat(1; 8; s1) et bat(1; 8; s2) aboutissent au mˆeme objet 3D. Seulement, il n’est pas ´evident pour un cache na¨ıf que bat(1; 8; s1) et bat(1; 8; s2) doivent ˆetre consid´er´es comme ´equivalents. 1Nous consid´erons ici deux bˆatiments comme identiques si leurs caract ´eristiques sont suffisamment proches pour pouvoir utiliser, pour les deux, un mˆeme repr´esentant, i.e. un mˆeme mod`ele g´eom´etrique. La m´ethode que nous proposons utilise des notions et m´ecanismes similaires `a ceux que l’on rencontre dans l’analyse de d´ependances entre les entr´ees et les sorties d’un programme [Pugh, 1989, Abadi, 1996]. Les travaux sur les d´ependances sont tr`es nombreux dans le domaine de la s´ecurit´e informatique, et de nombreux autres probl`emes du g´enie logiciel ont de forts liens avec l’analyse de d´ependance [Abadi, 1999]. Dans notre cas, ce type d’analyse permet par exemple de d´etecter que lorsque d est ´egale `a 1 le r´esultat final de la r´e´ecriture de l’axiome bat est d´ependant uniquement de ses deux premiers param`etres. Ainsi, le syst`eme de cache peut d´etecter automatiquement qu’il doit consid´erer bat(1; 8; s1) et bat(1; 8; s2) comme ´equivalents. Ce document pr´esente tout d’abord une version simplifi ´ee des FL-systems. En section 3, nous introduisons un calcul dynamique des d´ependances appliqu´e aux FLsystems permettant d’identifier les param`etres intervenant dans la r´e´ecriture d’un axiome. La section suivante pr´esente l’application de ce calcul `a la r´ealisation d’un cache intelligent pour la phase de r´e´ecriture des FLsystems. Enfin, nous concluons en section 5. 2 FL-SYSTEM Les FL-systems permettent de mod´eliser toutes sortes d’objets g´eom´etriques et, de pr´ef´erence, des objets qui pr´esentent une structure fortement redondante, tels des plantes ou des bˆatiments. En effet, bas´es sur une description grammaticale des objets, les FL-systems permettent d’utiliser `a bon escient la redondance de l’environnement mod´elis´e. Cette r´ep´etition nous permet des descriptions tr`es courtes et g´en´eriques de classes d’objets. Une classe d’objets est caract´eris´ee par un ensemble de param`etres. Une instance d’objet est alors obtenue par la d´efinition d’un ensemble de valeurs associ´ees `a ces param`etres. N´eanmoins, les valeurs donn´ees `a l’axiome d’un FLsystem n’ont pas n´ecessairement d’influence sur toutes les parties de l’objet g´en´er´e. Il est en effet courant qu’une mˆeme partie d’un objet se retrouve `a l’identique dans un autre objet de la mˆeme classe, malgr´e des diff´erences importantes dans les valeurs de leurs param`etres (on pourra ici penser aux feuilles de deux arbres de la mˆeme esp`ece). Dans cette section, nous proposons une grammaire et une s´emantique pour les r`egles de r´e´ecriture des FL-systems. 2.1 Le paradigme d’instanciation d’objets g´eom´etriques En informatique graphique, il est courant de vouloir r´eutiliser des objets g´eom´etriques d´ej`a d´efinis ailleurs, d’une part pour r´eduire l’espace de stockage, mais aussi le coˆut d’affichage de ces mod`eles lors de la navigation. Pour ce faire, Hart [Hart, 1992] propose le paradigme d’instanciation d’objets g´eom´etriques dans le cadre de la mod´elisation d’objets fractals. Ainsi, `a partir de r`egles simples, il devient possible de r´eutiliser lemˆeme arbre pour d´efinir une forˆet en comportant des centaines, chaque arbre pouvant lui-mˆeme utiliser plusieurs fois la mˆeme branche et ainsi de suite. Ce paradigme, aujourd’hui int´egr´e `a la plupart des langages de mod´elisation g´eom´etrique (e.g. VRML[Marrin, 1997]), am´eliore tr`es sensiblement les performances des applications 3D. Dans le cadre de la mod´elisation d’environnements naturels, Deussen [Deussen, 1998] propose l’instanciation approximative comme une extension du paradigme d’instanciation de Hart. Les objets mod´elis´es proc´eduralement sont projet´es dans l’espace des param`etres de l’axiome et ensuite regroup´es par clusters. Chaque cluster repr´esente alors une instance approximative de tous les objets qui en font partie, r´eduisant ainsi le nombre d’objets que contient la sc`ene. Les FL-systems permettent l’utilisation de noeuds VRML, et donc l’utilisation du paradigme d’instanciation. Nous proposons ici un m´ecanisme d’instanciation automatique, implicite, et transparent pour l’utilisateur. Ce m´ecanisme est bas´e sur le calcul dynamique des d´ependances dans les r`egles de r´e´ecriture (cf. section 3). 2.2 La grammaire utilis´ee Comme le montre la figure 1, ces r`egles sont compos ´ees d’une partie gauche (Axiome), d’une condition (Cond), et d’une partie droite (Terme). Nous consid´ererons ici que la strat´egie de r´e´ecriture est d´eterministe et r´ecursive `a gauche, contrairement aux L-systems dont la r´e´ecriture est parall`ele. Cette restriction a pour but de rendre d´eterministe chaque ´etape de la r´e´ecriture d’un axiome. Ceci simplifie la pr´esentation des travaux d´evelopp´es dans cet article, en particulier les th´eor`emes pr´esent´es en section 3. De plus, pour contrˆoler la profondeur de r´e´ecriture, nous utiliserons un param`etre donn´e `a l’axiome. Ainsi, `a partir d’un axiome donn´e, une r´e´ecriture engendrera un mot compos´e de terminaux. Les terminaux, dans les FL-systems, sont des fonctions et, plus g´en´eralement, des appels `a des primitives g´eom´etriques quelconques (VRML, OpenGL ou autres). Pour plus de d´etails sur les strat´egies de r´e´ecriture pouvant ˆetre appliqu´ees sur les FL-systems, ainsi que sur la nature des mots g´en´er´es, voir [Marvie, 2005]. La figure 2 donne un exemple de FL-system. L’axiome initialise les param`etres n, , l, w et color, qui repr´esentent respectivement la profondeur de r´e´ecritu2r,e un angle de rotation, la longueur d’un segment de branche, le rayon d’une branche et la couleur des feuilles. Les terminaux rotateX et rotateZ tournent le rep`ere local autour des axes X et Z respectivement. Le terminal moveZ d´eplace le rep`ere local le long de l’axe Z, tandis que les crochets [ et ] empilent et d´epilent le rep`ere local, permettant la cr´eation facile de sous-branches. Hormis pour l’axiome L, il existe 2 r`egles de r´e´ecriture pour tous les axiomes. La premi`ere repr´esente la r`egle de d´ecomposition de l’axiome `a proprement parler, l’autre nous assure que lorsque la profondeur de r´e´ecriture est atteinte, l’axiome est remplac´e par un terminal. 2La profondeur de r´e´ecriture d’un FL-system est son ˆage. ! : true ! A(7; 22:5; 10; 1; green) A(n; ; l;w; color) : n > 0 ! B(n 􀀀 1; ; l;w; color) rotateZ(  5) B(n 􀀀 1; ; l;w; color) rotateZ(  5) B(n 􀀀 1; ; l;w; color) A(n; ; l;w; color) : n = 0 !  B(n; ; l;w; color) : n > 0 ! [ rotateX() F(n 􀀀 1; ; l;w; color) L(n 􀀀 1; ; l;w; color) A(n 􀀀 1; ; l;w  0:707; color)] B(n; ; l;w; color) : n = 0 !  F(n; ; l;w; color) : n > 0 ! F(n 􀀀 1; ; l;w; color) L(n 􀀀 1; ; l;w; color) rotateZ(  5) F(n 􀀀 1; ; l;w; color) F(n; ; l;w; color) : n = 0 ! cylinder(l;w) moveZ(l) L(n; ; l;w; color) : true ! [ rotateX(􀀀  2) leaf(l; color) ] FIG. 2 – FL-system pour un buisson modifi´e `a partir d’un L-system propos´e par Prusinkiewicz[Prusinkiewicz, 1990] V al = R+ V ar ::= [a 􀀀 z] NAxiome ::= [A 􀀀 Z][a 􀀀 z]? NTerminal ::= [a 􀀀 z]? V ::= V ar j V al Terminal ::= NTerminal ( ExPar? ) OpCond ::= = j > Cond ::= V OpCond V j true OpP ar ::= + j 􀀀 j x ExPar ::= ( ExPar OpP ar ExPar ) j V Axiome ::= NAxiome ( ExPar? ) Terme ::= Axiome Terme j Terminal Terme j  RWrule ::= Axiome : Cond ! Terme FIG. 1 – Proposition de grammaire pour les r`egles de r´e´ecriture. A? d´esigne une liste d’´el´ements de type A. FIG. 3 – R´esultat de la r´e´ecriture du L-system de la figure 2 L’axiome A repr´esente un apex3 `a partir duquel sont cr´e´ees trois branches B. Les branches B se d´ecomposent en une arˆete F, une feuille L et un nouvel apex A. Chaque arˆete F est d´ecompos´ee en une arˆete, une feuille, et une autre arˆete, simulant ainsi le ph´enom`ene de croissance des branches. Lorsque la profondeur de r´e´ecriture est nulle, les arˆetes F sont remplac´ees par des cylindres (dont l’axe de rotation est l’axe Z) grˆace au terminal cylinder. Les feuilles L sont, quand `a elles, remplac´ees par des objets g´eom´etriques leaf. La figure 4 montre les diff´erences entre la strat´egie de r´e´ecriture parall`ele des L-systems et celle propos´ee ici pour les FL-systems. N´eanmoins, en s’assurant que les termes situ´es en partie droite des r`egles d´ecr´ementent le param`etre n de l’axiome, le r´esultat de la r´e´ecriture compl`ete du FL-system et du L-system ´equivalent apr`es n d´erivations successives sont identiques. La figure 3 montre une interpr´etation g´eom´etrique de la r´e´ecriture de cet exemple. 2.3 La s´emantique utilis´ee Pour pouvoir utiliser un cache pour les FL-systems, nous avons besoin d’une s´election des r`egles de r´e´ecriture d´eterministe. Nous proposons ici une formalisation de la s´emantique utilis´ee pour la r´e´ecriture des FL-systems. Tout d’abord, nous d´efinissons R comme la liste (ordonn ´ee) des r`egles de r´e´ecriture d’un FL-system donn´e. Soit A(v) un axiome tel que v 2 R. Nous avons alors la s´emantique donn´ee en figure 5. x 2 Terminaux? A(p) : c ^ c ! T = first(R;A(v)) x A(v) y !R x eval(T[v=p]) y FIG. 5 – S´emantique des r`egles de r´e´ecriture Nous d´efinissons first(R;A(v)) la fonction qui retourne la r`egle de r´e´ecriture “A(p ) : c ^ c ! T” o`u : – “A(p) : c ! T” = ri est la premi`ere r`egle de R s’appliquant (i.e. la condition “c” est vraie) `a A(v) – c est la conjonction des n´egations des condition des re`gles pr´ec´edantirdans R et s’appliquant a` un axiome de nom A X[a=b] retourne le r´esultat de la substitution dans X de toutes les occurrences de b par a. Ainsi, 3En biologie, apex est le nom donn´e au sommet d’un organe. A(7; 22:5; 10; 1; green) A(7; 22:5; 10; 1; green) B(6; ...) ... B(6; ...) ... B(6; ...) B(6; ...) ... B(6; ...) ... B(6; ...) [ ...F(5; ...) L(5; ...) A(5; ...) ] ...B(6; ...) ...B(6; ...) [ ...F(5; ...) L(5; ...) A(5; ...) ] ...[ ...F(5; ...) L(5; ...) A(5; ...) ] ...[ ...F(5; ...) L(5; ...) A(5; ...) ] [ ...B(4; ...) ...B(4; ...) ... B(4; ...)] ...B(6; ...) ...B(6; ...) [ ...B(4; ...) ...B(4; ...) ...B(4; ...)] ...[ ...B(4; ...) ...B(4; ...) ...B(4; ...)] ...[ ...B(4; ...) ...B(4; ...) ...B(4; ...)] ... ... FIG. 4 – Comparaison des strat´egies de r´e´ecriture entre les FL-systems propos´es (`a gauche) et les L-systems (`a droite). (A(x+3; y))[(1; 0)=(x; y)] rend A(1 + 3; 0). La fonction eval(T) parcourt le terme T et ´evalue les expressions y apparaissant pour obtenir les param`etres num´eriques de l’´etape de r´e´ecriture suivante. Par exemple, eval(A(1+3; 0)) retourne A(4; 0). Exemple 2.1: Un autre exemple de FL-system ! : true ! A(1; 2) A(m; n) : m = 0 !  A(m; n) : n > 0 ! A(m; n 􀀀 1) A(m; n) : n = 0 !  ´Etant donn´e le FL-system de l’exemple 2.1, l’axiome A(1; 2) se r´e´ecrit en A(1; 1) avec first(R;A(1; 2)) ´egal : A(m; n) : :(m = 0) ^ (n > 0) ! A(m; n 􀀀 1) Dans cet exemple, la condition m 6= 0 ^ n > 0 donne la d´ependance vis-`a-vis des variables m et n de la r`egle appliqu ´ee. Si la n´egation de m = 0 n’´etait pas pr´esente, l’importance de la valeur de m ne serait pas explicite. Toujours dans ce mˆeme exemple, la fonction eval remplace le param`etrem par sa valeur (1), et ´evalue l’expression “n 􀀀 1” `a 1. Le paragraphe pr´ec´edent aborde la d´ependance qui existe entre la valeur des param`etres d’un axiome et le r´esultat final de sa r´e´ecriture. La section suivante propose d’aller plus loin dans l’´etude de cette d´ependance. 3 CALCUL DYNAMIQUE DES DE´ PENDANCES Le syst`eme de cache pr´esent´e dans cet article met `a profit la propri´et´e de joignabile´itde certains termes dans un syst`eme de r´e´ecriture [Dershowitz, 1990]. Si deux termes se r´e´ecrivent en un mˆeme terme sous forme normale (i.e. compos´e uniquement de terminaux) alors ces deux termes sont dit joignables. Dans cette section, nous exposons la m´ethode employ´ee afin de calculer dynamiquement un ensemble, aussi grand que possible, de termes joignables avec le terme en cours de r´e´ecriture. La m´ethode employ´ee s’inspire de travaux sur la confidentialit ´e de programmes s´equentiels [Le Guernic, 2005]. Elle se base sur la modification de la s´emantique de r´e´ecriture afin de manipuler des valeurs ´etiquet´ees. La r´e´ecriture d’un axiome, param´etr´e par des valeurs´etiquet´ees, retourne alors un terme sous forme normale, lui-mˆeme marqu´e par une ´etiquette. Cette derni`ere d´ecoule des ´etiquettes des param`etres de l’axiome qui ont influenc´e cette r´e´ecriture. 3.1 Adaptation de la grammaire La simple modification, donn´ee en figure 6, de notre grammaire initiale (figure 1) permet d’int´egrer les valeurs ´etiquet´ees. P(Label) est l’ensemble des ensembles de Label. Les valeurs (V al) sont maintenant des paires not´ees “e : n”. Le deuxi`eme ´el´ement de cette paire (n), aussi appel´e valeur num´erique, appartient `a R+ (“n” est la valeur utilis´ee dans la section 2). Le premier ´el´ement (e) est un ensemble de labels formant une ´etiquette. Chaque ´etiquette de l’axiome initial4 est compos´ee d’un unique label. Chacun de ces labels n’est utilis´e qu’une seule fois (cf. exemple 3.1). Exemple 3.1: Exemple d’´etiquetage initial A(fl1g : 7; fl2g : 22:5; fl3g : 10; fl4g : 1; fl5g : green) Etiquette = P(Label) V al = (Etiquette  R+) FIG. 6 – Grammaire des valeurs ´etiquet´ees 3.2 Une nouvelle s´emantique La figure 7 pr´esente la nouvelle s´emantique utilis ´ee pour la r´e´ecriture de termes dont les valeurs sont ´etiquet´ees. Cette s´emantique pr´esente de grandes similitudes avec celle pr´esent´ee dans la section 2. Il est facile de se convaincre que, mis `a part les ´etiquettes, ces deux s´emantiques r´e´ecrivent un axiome de la mˆeme fac¸on. x 2 Terminaux? A(p) : c ^ c ! T = first(R;A(v)) ec = (var(c ^ c))[lab(v)=p] eop = (var(op(T)))[lab(v)=p] e0 = e [ ec [ eop e : x A(v) y !R e0 : x eval(T[v=p]) y FIG. 7 – Nouvelle s´emantique des r`egles de r´e´ecriture Dans cette nouvelle s´emantique, chaque terme est ´egalement ´etiquet´e. Cette ´etiquette identifie l’ensemble des param`etres qui ont influenc´e la r´e´ecriture. Afin de calculer la nouvelle ´etiquette d’un terme lors de sa r´e´ecriture, nous introduisons trois nouvelles fonctions : op retourne la liste des op´erations contenues dans son argument. Par exemple, “op(A(x; x + y; x + 1))” rend la liste [x + y; x + 1]. 4On appelle axiome initial, l’axiome servant de point de d´epart `a la r´e´ecriture. var retourne l’ensemble des variables de son argument. Ainsi “var(x > y)” rend en r´esultat l’ensemble fx; yg. lab extrait l’´etiquette d’une valeur. Par exemple, “lab(e : 3)” retourne l’´etiquette e. La fonction eval est ´egalement modifi´ee afin de calculer avec des valeurs ´etiquet´ees. Lors de l’´evaluation d’un param`etre sous forme d’op´eration, eval fait abstraction des ´etiquettes et effectue l’op´eration `a partir des valeurs num´eriques. Une nouvelle ´etiquette vide est attribu ´ee au r´esultat. Par exemple, eval(A((e:1) + 3)) retourne A(; : 4). Lors de la r´e´ecriture d’un terme t dans un terme 0 t (e : t !R e0 : t0), la nouvelle ´etiquette (e0) est obtenue en faisant l’union des labels de l’ancienne ´etiquette avec les labels des ´etiquettesec et eop, o`u : ec contient les labels des param`etres influenc¸ant la s´election de la r`egle de r´e´ecriture pour cette ´etape de l’´evaluation. eop contient les labels des param`etres qui sont intervenus dans le calcul d’une op´eration `a cette ´etape. 3.3 Propri´et´es de la s´emantique Afin d’´enoncer certaines propri´et´es de la s´emantique, une relation de projection entre param`etres est tout d’abord d´efinie. Cette d´efinition s’appuie sur une repr´esentation des param`etres d’un axiome sous la forme d’une liste de valeurs ´etiquet´ees. Cette projection s’exprime en fonction d’une ´etiquette qui restreint les param` etres pris en compte. On note “P1 e* P2” la projection d’une liste de param`etres P1 dans une autre liste de param`etres P2 de fac¸on restreinte `a l’´etiquette e. Pour toutes listes de param`etres P1 et P2, P1 e* P2 si et seulement si les param`etres, dont l’´etiquette dans P1 est un sous-ensemble de e, ont la mˆeme valeur num´erique dans P1 et dans P2. Une d´efinition r´ecursive plus formelle de cette notion est donn´ee dans la d´efinition 3.1. D´efinition 3.1 (P1 e* P2) – [ ] e* [ ] est vrai – [e1 : x j RP1] e* [ : y j RP2] est vrai ssi : 1. x = y ou e1 6 e 2. RP1 e* RP2 Trois nouvelles fonctions sont ´egalement introduites : labellise prend en argument une liste de valeurs num´eriques (n) et une liste d’´etiquettes de mˆeme longueur (e). Elle retourne une liste de valeurs ´etiquet´ees v de meˆme longueur que n. v est construite de telle sorte que, pour tout i, vi = ei : ni. Par exemple, labellise([e1; e2]; [5; 1]) retourne la liste [e1 : 5; e2 : 1]. strip prend en argument un terme T construit `a partir de valeurs ´etiquet´ees. Elle retourne un terme T’ qui correspond au terme T dont toutes les ´etiquettes ont ´et´e supprim´ees. Ainsi, strip(A1(:e2; e2 :7)) retourne A(2; 7). T[v=e]E est une fonction de substitution bas´ee sur les ´etiquettes. Le r´esultat de la substitution T[v =e]E est le terme T dans lequel les valeurs dont l’´etiquette apparaˆıt `a la position i dans la liste e ont ´et´e remplac ´ees par la valeur `a la position i dans v. Par exemple, A(e1 : 2; e2 : 7)[(ea : 3; eb : 4)=(e2; e3)]E est le terme A(e1 : 2; ea : 3). Th´eor`eme 3.1 (Stabilit´e une ´etape) 5 Pour tout ´etiquette ei, axiome A, liste de valeurs num´eriques n et d’´etiquettes ev telles que labellise(n; ev ) = v avec v param`etres de A, et ensemble de r`egles R, si : ei : A(v) !R e : T alors, pour toute liste de valeurs v 2 telle que v e* v 2 : : A(v 2) !R : T’ avec strip(T’) = strip(T[v 2=ev ]E). Le th´eor`eme 3.1 stipule que, si un axiome A param´etr´e par v se r´e´ecrit en une ´etape dans le terme T, alors le mˆeme axiome, param´etr´e par une liste de valeurs dans laquelle se projette v (restreint par l’´etiquette g´en´er´ee), se r´e´ecrit en une ´etape dans le terme T’. Les ´etiquettes mises `a part, ce terme T’ correspond au terme T dans lequel les valeurs ´etiquet´ees apparaissant dans v  ont ´et´e remplac ´ees par les valeurs correspondantes des param`etres utilis´es pour la r´e´ecriture de T’. Soit !y R la fermeture transitive de !R aboutissant `a un terme sous forme normale. Le corollaire 3.2 est la sp´ecialisation au cas o`u ei = ; de la g´en´eralisation du th´eor`eme 3.1 `a!y R. Corollaire 3.2 (Reconstructibilit´e) 6 Pour tout axiome A, liste de valeurs num´eriques n et d’´etiquettes ev telles que labellise(n; ev ) = v, avec v param`etres de A, et ensemble de r`egles R, si : ; : A(v) !y R e : NT alors, pour toute liste de valeurs v 2 telle que v e* v 2 : : A(v 2) !y R : NT’ avec strip(NT’) = strip(NT[v 2=ev ]E ). 3.4 Interpr´etation des ´etiquettes r´esultants de la r´e´ecriture d’un axiome Dans la suite de cet article, afin de simplifier la pr´esentation, les ´etiquettes attribu´ees aux param`etres de l’axiome initial7 sont index´ees sur les entiers. Ainsi, e1 est l’´etiquette du premier param`etre, e2 celle du deuxi`eme, et ainsi de suite. Chacune de ces ´etiquettes est 5La preuve du th´eor`eme 3.1 d´ecoule de l’aspect d´eterministe de la s´emantique utilis´ee. En effet, `a chaque ´etape de r´e´ecriture, seul l’axiome le plus `a gauche peut ˆetre r´e´ecrit. De plus, les ´etiquettes des valeurs intervenant dans l’´evaluation de la condition de la r`egle de r´e´ecriture retourn´ee par la fonction first font partie de l’´etiquette e g´en´er´ee lors de l’´etape de r´e´ecriture. Donc toute r´e´ecriture en une ´etape du mˆeme axiome avec des param`etres dans lesquels se projettent les param`etres v utilise la mˆeme r`egle de r´e´ecriture. 6La preuve du corollaire 3.2 s’obtient par sp´ecialisation au cas o`u ei = ; du corollaire ´equivalent o`uien’est pas restreint `a l’ensemble vide ; ce dernier corollaire ´etant prouv´e par r´ecurrence sur le nombre d’´etapes de la r´e´ecriture. 7On appelle axiome initial, l’axiome servant de point de d´epart `a la r´e´ecriture. compos´ee d’un label unique. Ces labels sont index´es de la mˆeme fac¸on que les ´etiquettes. Pour tout entier strictement positif i, ei = flig. L’exemple 3.2 pr´esente l’ensemble R des r`egles de r´e´ecriture qui sont utilis´ees dans cette partie. Exemple 3.2: Ensemble des r`egles des axiomes A et B A(c; d; x) : c = 0 ! B(d; x) A(c; d; x) : c > 0 ! v(d + c) A(c 􀀀 1; d; x) B(d; x) : d = 0 ! t() B(d; x) : d > 0 ! u(x) B((d 􀀀 1); x) Soit e l’´etiquette associ´ee au terme sous forme normal r´esultant de la r´e´ecriture de l’axiome C (; : C()v!y R e : NT). e contient les labels de tous les param`etres qui ont influenc´es la s´election des r`egles lors de la r´e´ecriture de C; et donc la structure g´en´era8ledu terme sous forme normale obtenu. Par exemple, l’´evaluation de l’axiome B(e1 : 0; e2 : 3) ext´eriorise9 uniquement l’´etiquette de son premier param`etre et rend le terme sous forme normale t(). Celui-ci n’ayant pas de terminaux avec param` etre, le corollaire 3.2 implique que tout axiome de la forme B( :0; ) se r´e´ecrit sous forme normale en t(). La r´e´ecriture de l’axiome A(1e: 0; e2 : 0; e3 : 3) fonctionne de fac¸on similaire. Le r´esultat de sa r´e´ecriture est donn´e dans l’exemple 3.3. L’interpr´etation du r´esultat de l’´evaluation de l’axiome A(e1 : 1; e2 : 1; e3 : 1) est plus complexe. Le r´esultat, donn´e dans l’exemple 3.3, montre l’ext´eriorisation des ´etiquettes des deux premiers param`etres uniquement. Il est donc possible d’en d´eduire que la r´e´ecriture sous forme normale de tout axiome de la forme A( :1; :1; ) rend un terme ayant pour structure g´en´erale v( ) u( ) t(). Plus pr´ecis´ement, le param`etre du terminal v ayant une ´etiquette vide, le corollaire 3.2 implique que le r´esultat a pour forme v( : 2) u( ) t(). De plus, ´etant donn´e que le param`etre de u a pour ´etiquette celle du troisi`eme param`etre de l’axiome initial, ce mˆeme corollaire implique que la r´e´ecriture sous forme normale de tout axiome de la forme A( :1; :1; :X) rend un terme ayant pour forme v( :2) u( :X) t(). Exemple 3.3: R´esultats d’´evaluations Le tableau suivant pr´esente des exemples d’´evaluation des axiomes A et B de l’exemple 3.2. Axiome R´esultat B(e1 : 0; e2 : 3) e1 : [t()] A(e1 : 0; e2 : 0; e3 : 3) fl1; l2g : [t()] A(e1 : 1; e2 : 1; e3 : 1) fl1; l2g : [v(; : 2); u(e3 : 1); t()] 4 APPLICATION `AUN SYST ` EME DE CACHE Comme pr´esent´e en introduction, le but de ce travail est d’acc´el´erer la r´e´ecriture des FL-systems pr´esent´es en section 2 `a l’aide d’un m´ecanisme de cache appel´e cache 8i.e. abstraction faite des param`etres des terminaux. 9On dit qu’une r´e´ecriture ext´eriorise une ´etiquette si cette ´etiquette se retrouve dans l’´etiquette du terme sous forme normale obtenu. intelligent. Dans un cache standard, la paire (clef,valeur) g´en´er´ee par une r´e´ecriture est tr`es simple. La clef correspond a l’axiome initial. Elle inclue le nom de l’axiome et la valeur effective de ses param`etres. La valeur associ´ee `a cette clef est le terme normal obtenu lors de la r´e´ecriture. Cependant, il arrive qu’un param`etre n’intervienne pas dans la r´e´ecriture de l’axiome, soit pour une raison de niveau de d´etails pris en compte, soit parce que le param` etre intervient uniquement au moment du rendu de l’environnement virtuel. Ainsi, avec les r`egles propos´ees dans l’exemple 3.2 et comme indiqu´e dans la section 3.4, les axiomes B(0; 3) et B(0; 0) se r´e´ecrivent de la mˆeme fac¸on. En utilisant un calcul de d´ependance comme celui qui est pr´esent´e en section 3, il est possible d’am´eliorer la couverture de la clef de fac¸on `a ce qu’une concordance soit d´eclench´ee dans le cache pour deux axiomes qui ne diff`erent que par la valeur de param`etres n’intervenant pas dans la r´e´ecriture de ces axiomes. Cette section pr´esente tout d’abord la m´ethode de g´en´eration des clefs ´etendues et des valeurs associ´ees. L’utilisation de ces paires (clef,valeur) est ensuite expliqu´ee et prouv´ee correcte au regard de la s´emantique donn´ee dans la figure 7. Enfin, la question de la gestion des paires (clef,valeur) est abord´ee. 4.1 G´en´eration des clefs et valeurs Comme expos´e en section 3, `a partir de l’´evaluation d’un axiome param´etr´e A, il est possible de d´eterminer automatiquement la forme normale r´e´ecrite d’un ensemble A d’axiomes de mˆeme nom (A) mais param´etr´es par des valeurs diff´erentes. Afin d’utiliser ce r´esultat dans un syst`eme de cache, il reste `a d´efinir une m´ethode pour extraire automatiquement une clef caract´erisant l’ensemble A. Il convient ´egalement de d´eterminer la valeur qui doit ˆetre associ´ee `a cette clef. Pour ce faire, deux nouvelles fonctions sont d´efinies : gen prend en argument une liste de valeurs ´etiquet´ees v et une ´etiquette e. Elle renvoie une liste de valeurs non-´etiquet´ees. L’´el´ement `a la position i est la valeur num´erique de vi 10 si l’´etiquette de vi est un sous-ensemble de e, et sinon. Par exemple, gen([fl1g:1; fl2g:1; fl3g: 1]; fl1; l2g) renvoie [1; 1; ]. ffonc prend en argument un terme sous forme normale T dont les terminaux le composant sont param´etr´es par des valeurs ´etiquet´ees. Elle renvoie un terme sous forme normale T’ dont les terminaux le composant sont param´etr´es par des valeurs non-´etiquet´ees. tj est le terminal `a la position j dans T, et t0 j celui `a la mˆeme position dans T’. t0 j est construit `a partir de tj en remplac¸ant chaque valeur e : v par #i si l’´etiquette e est compos´ee uniquement du labeli,let v sinon. Par exemple, ffonc(v(;:2) u(fl3g:1) t()) renvoie “v(2) u(#3) t()”. Lors d’une r´e´ecriture sous forme normale d’un axiome A param´etr´e par les valeurs ´etiquet´ees:v ; : A(v) !y R e : NT 10vi est l’´el´ement `a la position i dans v . le m´ecanisme de cache g´en`ere la clef A(gen(v ; e)) et y associe la valeur ffonc(NT). L’exemple 4.1 montre les paires (clef,valeur) g´en´er´ees par les ex´ecutions de l’exemple 3.3. Exemple 4.1: Mise en cache En utilisant les ´evaluations pr´esent´ees dans l’exemple 3.3, le tableau suivant pr´esente les couples (clef,valeur) mis en cache : Clef Valeur B(0; ) [t()] A(0; 0; ) [t()] A(1; 1; ) [v(2); u(#3); t()] 4.2 Utilisation du cache Afin de formaliser l’utilisation du cache, une relation de concr´etisation entre listes d’´el´ements appartenant `a R+ [ f g est introduite dans la d´efinition 4.1. Une liste lc est une concr´etisation d’une liste de mˆeme longueurla, not´e lc  la, si et seulement si tout ´el´ement appartenant `a R+ dans la est ´egal `a l’´el´ement `a la mˆeme position dans lc. D´efinition 4.1 (lc  la) – [ ]  [ ] est vrai – [x j RLc]  [y j RLa] est vrai ssi : 1. y = x ou y = 2. RLc  RLa On d´efinit ´egalement une fonction not´ee T[]v]. T est un terme sous forme normale et v est une liste de valeurs non-´etiquet´ees. Cette fonction substitue dans T les identifiants introduits par la fonction ffonc (#i) par la valeur `a la position i dans la liste de valeurs v. Le cache est utilis´e de la fac¸on suivante. On consid`ere la r´e´ecriture de l’axiome A param´etr´e par les valeurs non- ´etiquet´eesv c . Si il existe une paire (A(v a), T) telle que v c  v a, alors le cache rend directement le terme sous forme normale T[v c ]]. La correction du m´ecanisme est prouv´ee par le th´eor`eme 4.3 ; lui-mˆeme prouv´e `a l’aide des lemmes 4.1 et 4.2 ainsi que du corollaire 3.2. Le r´esultat de l’application du m´ecanisme de cache `a la r´e´ecriture de l’axiomeA(1; 1; 5), lorsque A(1; 1; 3) a d´ej`a ´et´e r´e´ecrit, se trouve dans l’exemple 4.2. Exemple 4.2: Utilisation du cache Dans cet exemple, le cache contient les paires (clef, valeur) donn´ees dans le tableau de l’exemple 4.1. Entre autre, il contient la paire (A(1; 1; ), [v(2); u(#3); t()]). La liste de valeur (1; 1; 5) est une concr´etisation de la liste (1; 1; ). Ainsi, pour l’axiome A(1; 1; 5), le cache rend le terme sous forme normale [v(2); u(5); t()]. Lemme 4.1 (Correction de la clef) Pour toute ´etiquette e et toutes listes de valeurs ´etiquet´ees v c et v a : v c  gen(v a; e) ) v a e* v c Lemme 4.2 (Correction de la valeur) Pour tout terme sous forme normale NT, liste de valeurs ´etiquet´ees v 2 , et liste d’´etiquettes e telle que l’´etiquette `a la position i est compos´ee d’un label unique indic´e `a la valeur i : strip(NT[v 2=e]E) = (ffonc(NT))[v 2 ]] Th´eor`eme 4.3 (Correction du cache) Pour tout axiome A, liste de valeurs num´eriques de longueur k n1 et n2 , et liste d’´etiquettes de longueur k e telle que l’´etiquette `a la position i est compos´ee d’un label unique indic´e `a la valeur i, si : 1. ; : A(labellise(n1 ; e)) !y R ef : NT 2. ; : A(labellise(n2 ; )) !y R : NT’ 3. n2  gen(labellise(n1 ; e); ef ) alors : strip(NT’) = (ffonc(NT))[n2 ]] 4.3 Gestion du cache Comme pour tout syst`eme de cache, les performances du m´ecanisme propos´e sont fortement d´ependantes de la gestion des paires (clef,valeur) par le cache. En effet, si le cache contient trop de paires, le syst`eme risque de passer plus de temps `a essayer de faire correspondre l’axiome `a r´e´ecrire avec une clef du cache qu’`a r´e´ecrire l’axiome en question. La r´esolution de ce probl`eme s’op`ere `a deux niveaux. Tout d’abord, il convient de limiter le nombre de paires (clef,valeur) candidates `a l’insertion dans le cache. Enfin, le remplacement de paires pr´esentes dans le cache par de nouvelles doit s’effectuer intelligemment. Le m´ecanisme de g´en´eration de clefs en produit ´enorm´ement.`A chaque niveau de r´e´ecriture d’un axiome, une paire (clef,valeur) est g´en´er´ee. Il convient de limiter cette prolif´eration en ignorant les paires correspondant `a un nombre r´eduit d’´etapes de r´e´ecriture. Entre autre, les clefs produites lors d’une r´e´ecriture utilisant une r`egle dont le membre droit est compos´e exclusivement de terminaux doivent ˆetre ignor´ees. Il est ´egalement n´ecessaire de ne pas prendre en compte les paires produites lors de la r´e´ecriture d’axiomes rencontr´es exceptionnellement. Une grande part de ce travail peut ˆetre effectu´ee par l’auteur d’un FL-system en indiquant les axiomes les plus g´en´eriques, et donc int´eressant a prendre en compte pour la g´en´eration des clefs. Par exemple, un FL-system produisant une forˆet fera une utilisation intensive de l’axiome g´en´erant un arbre. Celui-ci ´etant, `a priori, relativement complexe, il est int´eressant de le prendre en compte pour la g´en´eration des paires. Lors de l’insertion d’une paire dans le cache, les m´ethodes standards utilis´ees dans le domaine des caches permettent d’am´eliorer les performances. De plus, le m´ecanisme introduit dans cet article permet, dans certains cas, d’augmenter les capacit´es du cache sans augmenter sa taille. En effet, lors de l’entr´ee dans le cache d’une paire (c; v), si il existe une paire (c0; v0) pr´esente dans le cache telle que c0  c, il est possible de remplacer (c0; v0) par (c; v) sans perte d’expressivit´e du cache. En effet, la valeur v0 peut ˆetre r´eg´en´er´ee `a partir de v. ! : true ! I(30; 6; 22:5; 10; 1; green) I(f; n; ; l;w; color) : f > 0 ! A(n;  + rand(􀀀2; 2)  0:5; l + rand(􀀀10; 10)  0:1; w + rand(􀀀2; 2)  0:5; color + rand(􀀀10; 10)  0:1) I(f 􀀀 1; n; ; l; w; color) I(f; n; ; l;w; color) : f = 0 !  FIG. 8 – FL-system pour une forˆet de buissons utilisant le FL-system de la figure 2 (pour simplifier, nous consid´erons ici les couleurs comme des r´eels. green est donc une constante r´eelle.) 4.4 R´esultats exp´erimentaux Le FL-system pr´esent´e en figure 8 ´etend celui de la figure 2. Le param`etre f est ajout´e afin de permettre un dimensionnement facile du nombre de buissons engendr´es par le FL-system. Les param`etres suivants sont les mˆemes qu’auparavant, mais ils sont ici utilis´es en combinaison avec la fonction rand(a; b) qui d´elivre un entier compris entre a et b (inclus). Cette derni`ere introduit ainsi du bruit dans les param`etres des buissons g´en´er´es. Un prototype utilisant ce FL-system a ´et´e impl´ement´e en Prolog. Il simule la r´e´ecriture d’un terme sans cache, avec un cache simple, ou avec le syst`eme de cache propos´e dans cet article. Chaque clef du cache simple se compose du nom de l’axiome et des valeurs effectives des param`etres de l’axiome ayant servi `a la g´en´eration de cette clef. La figure 9 donne, en fonction du type de cache utilis´e, le temps de r´e´ecriture d’un terme correspondant `a trente buissons de structure identique mais de formes et de couleurs diff´erentes, i.e. des axiomes A ayant des valeurs diff´erentes pour les param`etre , l, w et color. L’axe des abscisses repr´esente la profondeur de r´e´ecriture, c’est- `a-dire la valeur du param`etre n donn´e aux axiomes, ou l’ˆage des buissons. Le gain en temps de r´e´ecriture entre la m´ethode propos´ee dans cet article et la m´ethode sans cache est illustr´e par la figure 10. La figure 11, quant `a elle, montre ce mˆeme gain, mais pour des r´e´ecritures de profondeur de 10 et un nombre de buissons variant de 10 `a 30. On constate sur les figures 9 et 10 que, plus la profondeur augmente, plus notre syst`eme de cache s’av`ere efficace. En effet, par rapport `a la r´e´ecriture sans cache, la m´ethode propos´ee ici profite alors de l’augmentation du nombre de termes joignables11 rencontr´es lors de la r´e´ecriture d’un buisson, c’est-`a-dire de la r´ep´etition dans sa structure. La figure 11 permet de constater que la grande g´en´ericit´e des clefs produites permet de g´en´erer certains buissons directement `a partir du cache, c’est-`adire de tirer partie de l’augmentation de la probabilit´e de retrouver des objets joignables lorsque la taille de l’environnement augmente. Ceci n’est pas possible avec le cache simple. Par contre, lors d’une r´e´ecriture ne rencontrant pas de termes aboutissant `a la mˆeme liste d’appel de primitives graphiques, notre syst`eme de cache est alors ´equivalent au cache simple, voire l´eg`erement plus lent. De fait, les efforts de g´en´eralisation des clefs seraient alors une tˆache vaine. 11Deux termes sont dits joignables si leur r´e´ecriture aboutit au mˆeme terme sous forme normale Enfin, il est `a noter que la gestion des m´ecanismes de base du cache par le prototype est tr`es mauvaise. Ceci est refl´et´e par les mauvais r´esultats du cache simple. Le prototype de cache intelligent dispose du mˆeme encodage des m´ecanismes de base, ce qui laisse `a penser qu’une meilleure impl´ementation permettrait d’obtenir de bien meilleurs r´esultats. 0 500 1000 1500 2000 2500 2 3 4 5 6 7 8 9 10 sans cache avec cache simple avec cache intelligent PSfrag replacements Comparaison des temps de r´e´ecriture avec et sans cache Temps de la r´e´ecriture en ms Profondeur de r´e´ecriture (n) FIG. 9 – Temps de r´e´ecriture pour un nombre de buissons f=30 et une profondeur de n variant de 2 `a 10. -30 -25 -20 -15 -10 -5 0 5 10 15 20 25 2 3 4 5 6 7 8 9 10 Gain en % PSfrag replacements Gain des temps de r´e´ecriture avec et sans cache Gain en % Profondeur de r´e´ecriture (n) (avec 30 buissons) FIG. 10 – Gain en temps de la r´e´ecriture avec cache intelligent pour l’exp´erience de la figure 9 5 CONCLUSION Dans cet article, nous avons pr´esent´e un syst`eme de cache intelligent bas´e sur un calcul dynamique des d´ependances. Ce nouveau syst`eme de cache pour les FL-systems permet une abstraction dans le processus de 18.5 19 19.5 20 20.5 21 21.5 22 22.5 23 10 15 20 25 30 Gain en % PSfrag replacements Gain des temps de r´e´ecriture avec et sans cache Gain en % Nombre de buissons (f) FIG. 11 – Gain de la r´e´ecriture pour une profondeur de n=10 et un nombre de buissons f variant de 10 `a 30. mod´elisation g´eom´etrique : la r´eutilisation des objets g´eom´etriques, jusqu’ici explicite, devient implicite. Cette technique est plus efficace qu’un cache simple grˆace `a la plus grande g´en´ericit´e des paires (clef,valeur) produites. Il est ainsi possible de g´en´erer la forme normale d’un axiome qui n’a pas encore ´et´e rencontr´e par instanciation de la valeur associ´ee `a une clef proche de l’axiome `a ´evaluer. N´eanmoins, il est important de ne pas calculer des paires (clef,valeur) pour chaque r`egle de r´e´ecriture, sous peine de faire d´eborder le cache. Une solution `a ce probl`eme consiste `a identifier, lors de l’´ecriture d’un FL-system ou automatiquement par analyse, les r`egles les plus utilis´ees. Notre syst`eme de cache permet une plus grande souplesse dans la mod´elisation d’environnements tr`es complexes. En effet, consid´erons un FL-system repr´esentant un arbre et poss´edant un param`etre color stipulant la couleur des feuilles de celui-ci. Une fois cet arbre utilis´e pour mod´eliser une forˆet, la modification de la valeur du param` etre color pourra ˆetre prise en compte de fac¸on automatique par le cache. L’objet repr´esentant une feuille sera alors modifi´e pour prendre en compte le changement de couleur, et le reste de la forˆet changera de couleur par la mˆeme occasion, sans modifier le cache et sans n´ecessiter la moindre r´e´ecriture. Enfin, le FL-system Intelligent Cache constitue une optimisation compl´ementaire `a l’instanciation approximative [Deussen, 1998]. En effet, notre technique permet de d´eterminer quels sont les termes dont les r´e´ecritures sont identiques, tandis que l’instanciation approximative permet de regrouper des termes similaires grˆace `a leur proximit´e dans l’espace des valeurs des param`etres. Notre technique permettrait donc d’am´eliorer le clustering en identifiant les param`etres qui n’influencent pas la r´e´ecriture. En effet, ceci revient `a r´eduire la dimension de l’espace des param`etres et, de ce fait, `a faciliter le clustering. Le couplage de ces deux technique constitue donc une perspective int´eressante pour nos prochains travaux. BIBLIOGRAPHIE [Abadi, 1999] Abadi M., Banerjee A., Heintze N. et Riecke J. G., A core calculus of dependency. Proceedings of the ACM Symposium Principles of Programming Languages, pages 147–160. [Abadi, 1996] Abadi M., Lampson B. et L´evy J.-J., Analysis and caching of dependencies. Proceedings of the 1996 ACM International Conference on Functional Programming, pages 83–91. ACM Press. [Dershowitz, 1990] Dershowitz N. et Jouannaud J.-P., Rewrite systems. In van Leeuwen J., editor, Handbook of Theoretical Computer Science, Volume B : Formal Models and Sematics (B), pages 243–320. North-Holland, Amsterdam. [Deussen, 1998] Deussen O., Hanrahan P., Lintermann B., Mˇech R., Pharr M. et Prusinkiewicz P., Realistic modeling and rendering of plant ecosystems. SIGGRAPH ’98 : Proceedings of the 25th annual conference on Computer graphics and interactive techniques, pages 275– 286, New York, NY, USA. ACM Press. [Hart, 1992] Hart J. C., The object instancing paradigm for linear fractal modeling. Proceedings of the conference on Graphics interface ’92, pages 224–231. Morgan Kaufmann Publishers Inc. [Le Guernic, 2005] Le Guernic G. et Jensen T., Monitoring information flow. In Sabelfeld A., editor, Proceedings of the 2005Workshop on Foundations of Computer Security. DePaul University. LICS’05 Affiliated Workshop. [Lindenmayer, 1968] Lindenmayer A., Mathematical models for cellular interactions in development, I & II. Journal of Theoretic Biology, 18 :280–315. [Marrin, 1997] Marrin C., Carey R. et Bell G., A vrml specification. Rapport technique, VRML consortium. http ://www.vrml.org/Sprecifications/VRML97. [Marvie, 2003] Marvie J.-E., Perret J. et Bouatouch K., Remote interactive walkthrough of city models. Proceedings of Pacific Graphics, volume 2, pages 389–393. [Marvie, 2005] Marvie J.-E., Perret J. et Bouatouch K., Fl-system : A functional l-system for procedural geometric modeling. The Visual Computer. To appear. [Prusinkiewicz, 1990] Prusinkiewicz P., Lindenmayer A., Hanan J. S. et al., The algorithmic beauty of plants. Springer-Verlag, New York. [Prusinkiewicz, 2001] Prusinkiewicz P., Mundermann L., Karwowski R. et Lane B., The use of positional information in the modeling of plants. Proceedings of Computer Graphics and Interactive Techniques, pages 289–300. ACM Press. [Pugh, 1989] Pugh W. et Teitelbaum T., Incremental computation via function caching. Conference Record of the 16th Annual Symposium on POPL, pages 315–328. [Smith, 1984] Smith A. R., Plants, fractals, and formal languages. Proceedings of the 11th annual conference on Computer graphics and interactive techniques, pages 1–10. ACM Press. Pierre Weis Xavier Leroy LE LANGAGE CAML Deuxi`eme ´edition Copyright 1992, 1993, 2009 Pierre Weis et Xavier Leroy. Ce texte est distribu´e sous les termes de la licence Creative Commons BY-NC-SA. Le texte complet de la licence est disponible `a l’adresse suivante : http://creativecommons.org/licenses/by-nc-sa/2.0/fr/legalcode Voici un r´esum´e des droits et conditions de cette licence. • Vous ˆetes libres : – de reproduire, distribuer et communiquer cette cr´eation au public – de modifier cette cr´eation • Selon les conditions suivantes : – Paternit´e. Vous devez citer le nom de l’auteur original de la mani`ere indiqu´ee par l’auteur de l’oeuvre ou le titulaire des droits qui vous conf`ere cette autorisation (mais pas d’une mani`ere qui sugg´ererait qu’ils vous soutiennent ou approuvent votre utilisation de l’oeuvre). – Pas d’Utilisation Commerciale. Vous n’avez pas le droit d’utiliser cette cr´eation `a des fins commerciales. – Partage des Conditions Initiales `a l’Identique. Si vous modifiez, transformez ou adaptez cette cr´eation, vous n’avez le droit de distribuer la cr´eation qui en r´esulte que sous un contrat identique `a celui-ci. • A chaque r´eutilisation ou distribution de cette cr´eation, vous devez faire appara ˆıtre clairement au public les conditions contractuelles de sa mise `a disposition. La meilleure mani`ere de les indiquer est un lien la page Web ci-dessus. • Chacune de ces conditions peut ˆetre lev´ee si vous obtenez l’autorisation du titulaire des droits sur cette oeuvre. • Rien dans ce contrat ne diminue ou ne restreint le droit moral de l’auteur ou des auteurs. `A mes parents, `A Suzanne et Michel, `A Lise, Marie, Jean-Baptiste et Ir`ene, `A H´el`ene. Pierre Weis Table des mati`eres Avant-propos xi I Programmer en Caml 1 Avertissement 3 1 Premiers pas 5 1.1 Id´ees g´en´erales sur Caml 5 1.2 Dialoguer avec Caml 6 1.3 Les d´efinitions 6 1.4 Fonctions 8 1.5 Valeurs et programmes 13 1.6 Impression 13 1.7 Conventions syntaxiques 15 1.8 Diagrammes syntaxiques 17 2 R´ecursivit´e 19 2.1 Fonctions r´ecursives simples 19 2.2 D´efinitions par cas : le filtrage 27 2.3 Les tours de Hanoi 28 2.4 Notions de complexit´e 31 3 Programmation imp´erative 37 3.1 La programmation imp´erative 37 3.2 Boucles 39 3.3 Manipulation de polynˆomes 40 3.4 Impression des polynˆomes 42 3.5 Caract`eres et chaˆınes de caract`eres 46 3.6 Les r´ef´erences 47 3.7 Un programme utilisant des r´ef´erences 49 3.8 R´ecursivit´e et boucles 50 3.9 R`egle d’extensionnalit´e 52 3.10 Effets et ´evaluation 53 4 Fonctionnelles et polymorphisme 57 4.1 Notion de polymorphisme 57 4.2 Fonctions d’ordre sup´erieur 59 4.3 Typage et polymorphisme 61 4.4 Curryfication 64 viii Table des mati`eres 4.5 Une fonctionnelle de tri polymorphe 65 4.6 La pleine fonctionnalit´e 67 4.7 Composition de fonctions 70 5 Listes 75 5.1 Pr´esentation 75 5.2 Programmation assist´ee par filtrage 77 5.3 Tri par insertion 78 5.4 Fonctionnelles simples sur les listes 81 5.5 Les polynˆomes creux 83 5.6 Filtrage explicite 84 5.7 Op´erations sur les polynˆomes creux 85 5.8 Animation des tours de Hanoi 88 5.9 Fonctionnelles complexes sur les listes 91 5.10 Efficacit´e des fonctions sur les listes : ´etude de cas 98 5.11 Listes et r´ecurrence 103 5.12 `A la recherche de l’it´erateur unique 105 6 Les structures de donn´ees 109 6.1 Polynˆomes pleins et polynˆomes creux 109 6.2 Types sommes ´elabor´es 113 6.3 Les types somme 116 6.4 Les types produit 116 6.5 M´elange de types somme et types produit 118 6.6 Structures de donn´ees mutables 118 6.7 Structures de donn´ees et filtrage 120 6.8 Structures de donn´ees et r´ecurrence 122 7 Le docteur 125 7.1 Vue d’ensemble 125 7.2 Les exceptions 126 7.3 Fonctions de recherche dans les listes 130 7.4 Traitements de chaˆınes de caract`eres 133 7.5 Cam´elia 135 7.6 Dialogue avec l’utilisateur 140 7.7 Exemple de session 143 7.8 Pour aller plus loin 144 8 Graphisme 147 8.1 Fractales 147 8.2 Le graphisme de Caml 148 8.3 Les nombres en repr´esentation flottante 149 8.4 Le crayon ´electronique 149 8.5 Premiers dessins 152 8.6 Le flocon de von Koch 154 9 Syntaxe abstraite, syntaxe concr`ete 155 9.1 Pr´esentation 155 9.2 Le retard `a l’´evaluation 156 ix 9.3 L’´evaluation des ordres du langage graphique 158 9.4 Syntaxe et s´emantique 159 9.5 Notions d’analyses syntaxique et lexicale 160 9.6 Analyse lexicale et syntaxique 161 9.7 Ajout des proc´edures 168 10 Programmes ind´ependants et modules 179 10.1 Chargement de fichiers 179 10.2 Programmes ind´ependants 180 10.3 Entr´ees-sorties de base 181 10.4 Programmes en plusieurs modules 183 10.5 Interfaces de modules 187 10.6 Compilations interactives 190 11 Interfaces graphiques 193 11.1 Structure d’une interface graphique 193 11.2 Relier des composants entre eux 194 11.3 Un convertisseur de devises 196 11.4 Le jeu du taquin 199 11.5 Pour aller plus loin 201 II Exemples complets 203 Avertissement 205 12 D´emonstration de propositions 207 12.1 La logique math´ematique 207 12.2 Calculs de tables de v´erit´e 210 12.3 Le principe des d´emonstrations 212 12.4 Repr´esentation et v´erification des propositions 213 12.5 Syntaxe concr`ete des propositions 217 12.6 Le v´erificateur de tautologies 221 12.7 Exemples de th´eor`emes 223 12.8 Pour aller plus loin : l’analyseur lexical universel 228 12.9 Pour aller encore plus loin : le hachage 232 13 Compression de fichiers 237 13.1 La compression de donn´ees 237 13.2 Plan du programme 238 13.3 L’algorithme de Huffman 240 13.4 Annexes 247 13.5 Mise en pratique 252 13.6 Pour aller plus loin 252 14 Simulation d’un processeur 255 14.1 Le pico-processeur 255 14.2 Le simulateur 260 14.3 L’assembleur 267 14.4 Pour aller plus loin 275 x Table des mati`eres 15 Compilation de mini-Pascal 277 15.1 Syntaxe abstraite, syntaxe concr`ete 277 15.2 Typage 283 15.3 Compilation 289 15.4 Pour aller plus loin 304 16 Recherche de motifs dans un texte 305 16.1 Les motifs 305 16.2 Syntaxe abstraite et syntaxe concr`ete des motifs 306 16.3 Les automates 309 16.4 Des expressions rationnelles aux automates 310 16.5 D´eterminisation de l’automate 313 16.6 R´ealisation de la commande grep 319 16.7 Annexe 320 16.8 Mise en pratique 321 16.9 Pour aller plus loin 321 III Introspection 323 17 Ex´ecution d’un langage fonctionnel 325 17.1 Le langage mini-Caml 325 17.2 L’´evaluateur 326 17.3 La boucle d’interaction 331 17.4 Mise en oeuvre 333 17.5 Pour aller plus loin 334 17.6 Annexe 336 18 Un synth´etiseur de types 339 18.1 Principes de la synth`ese de types 339 18.2 L’algorithme de synth`ese de types 344 18.3 Repr´esentation des types 348 18.4 L’unification 353 18.5 Inconnues, g´en´eralisation et sp´ecialisation 356 18.6 Impression des types 357 18.7 La boucle d’interaction 358 18.8 Mise en oeuvre 359 18.9 Pour aller plus loin 360 19 En guise de conclusion 365 19.1 Une m´ethodologie de programmation 365 19.2 La compilation de Caml 367 Index 373 Avant-propos On prononce Caml avec le « ca » de caf´e et le «mel » de melba. aml est un langage de programmation de conception r´ecente qui r´eussit `a ˆetre `a la fois tr`es puissant et cependant simple `a comprendre. Issu d’une longue r´eflexion sur les langages de programmation, Caml s’organise autour d’un petit nombre de notions de base, chacune facile `a comprendre, et dont la combinaison se r´ev`ele extrˆemement f´econde. La simplicit´e et la rigueur de Caml lui valent une popularit´e grandissante dans l’enseignement de l’informatique, en particulier comme premier langage dans des cours d’initiation `a la programmation. Son expressivit´e et sa puissance en font un langage de choix dans les laboratoires de recherche, o`u il a ´et´e utilis´e pour traiter des probl`emes parmi les plus ardus de l’informatique : d´emonstration assist´ee par ordinateur, analyses automatique de programmes, syst`emes de r´e´ecriture, compilation et m´etacompilation. En bref, Caml est un langage facile avec lequel on r´esout des probl`emes difficiles. Longtemps r´eserv´e `a de grosses machines coˆuteuses, le langage Caml est maintenant disponible gratuitement sur toute une gamme de machines, du micro-ordinateur personnel (PC, Macintosh, . . . ) aux stations de travail les plus puissantes, ce qui le rend accessible `a un vaste public, de l’amateur curieux au professionnel chevronn´e en passant par l’´etudiant informaticien. `A ce vaste public, Caml apporte une nouvelle approche de la programmation, des plus fructueuses. L’investissement que vous ferez en apprenant Caml ne sera pas vain : vous constaterez que le langage vous ouvre des horizons nouveaux et qu’il est assez puissant pour que vous y exprimiez simplement des id´ees complexes. Ce qui se con¸coit bien s’´enonce clairement et les programmes pour le dire vous viennent ais´ement en Caml. Ce livre se propose donc de faire d´ecouvrir Caml `a tous ceux qui s’int´eressent `a la programmation. Nous nous sommes efforc´es d’´ecrire un livre accessible `a tout « honnˆete homme », mais qui permette cependant de maˆıtriser le langage et d’en saisir les beaut´es. Pour ce faire, nous avons combin´e une introduction progressive aux principaux traits du langage avec un v´eritable cours de programmation, illustr´e de tr`es nombreux exemples de programmes qui vous permettront de saisir comment on utilise Caml et de vous approprier petit `a petit ce merveilleux outil. Les exemples vont jusqu’au d´eveloppement de programmes complets et d’une longueur respectable. Nous nous effor¸cons de justifier ces exemples, en les repla¸cant dans leur contexte et en analysant la clart´e et l’efficacit´e xii Avant-propos des solutions propos´ees. Cet ouvrage s’organise comme suit : • La partie I, «Programmer en Caml », introduit progressivement les traits du langage et les m´ethodes essentielles de programmation en Caml. • La partie II, «Exemples complets », montre comment r´esoudre en Caml un certain nombre de probl`emes r´ealistes de programmation. • La partie III, « Introspection », ´ebauche une impl´ementation de Caml en Caml, expliquant ainsi le typage et l’´evaluation de Caml. En compl´ement de ce livre, les auteurs ont ´ecrit un second ouvrage, intitul´e Manuel de r´ef´erence du langage Caml et publi´e par le mˆeme ´editeur, contenant tout ce qui est n´ecessaire au programmeur Caml exp´eriment´e : un manuel de r´ef´erence du langage Caml et un manuel d’utilisation du syst`eme Caml Light, le compilateur Caml que nous utilisons dans ce livre. Les deux livres sont con¸cus pour ˆetre utilis´es ensemble : le pr´esent ouvrage renvoie au manuel de r´ef´erence pour une description exhaustive du langage et des explications d´etaill´ees de certains points techniques ; le manuel de r´ef´erence suppose connues les notions introduites dans cet ouvrage. Tous les exemples de ce livre sont pr´esent´es dans le syst`eme Caml Light, un environnement de programmation en Caml fonctionnant `a la fois sur micro-ordinateurs (Macintosh et PC) et sur mini-ordinateurs et stations de travail Unix. Il existe d’autres impl´ementations du langage Caml, comme par exemple Objective Caml, qui ajoute `a Caml Light des objets et des classes, ainsi qu’un syst`eme de modules plus puissant. L’essentiel de ce qui est dit dans ce livre porte sur le langage et s’applique donc `a toutes les impl´ementations. Nous signalerons les quelques points sp´ecifiques au syst`eme Caml Light. Les lecteurs qui souhaitent consulter la documentation compl`ete du syst`eme Caml Light peuvent se reporter au Manuel de r´ef´erence du langage Caml, ou `a notre site Web http://caml.inria.fr/. Le syst`eme Caml Light est distribu´e gratuitement et peut ˆetre reproduit librement `a des fins non commerciales. Pour ceux qui ont acc`es au r´eseau Internet, Caml Light est disponible sur le Web `a l’adresse http://caml.inria.fr/. L’Institut National de Recherche en Informatique et en Automatique (INRIA) en assure ´egalement la distribution sur c´ed´eroms. Pour obtenir ce c´ed´erom, reportez-vous `a l’encadr´e qui figure en page de copyright. Nous encourageons le lecteur `a se procurer le syst`eme Caml Light et `a l’installer sur sa machine, suivant les instructions donn´ees par exemple dans le chapitre 12 du Manuel de r´ef´erence. Il pourra ainsi essayer les exemples et exp´erimenter par lui-mˆeme, ce qui lui facilitera grandement la lecture de ce livre. Remerciements Nous tenons `a remercier Christian Queinnec, Bernard Serpette et G´erard Huet qui se sont astreints `a relire ce livre, Val´erie M´enissier-Morain qui a particip´e `a l’illustration, Ian Jacobs pour son assistance typographique et Christian Rinderknecht qui a restaur´e les lettrines, une calligraphie anglaise du huiti`eme si`ecle. Le jeu de taquin de la section 11.4 est dˆu `a Fran¸cois Rouaix ; l’exemple de la section 11.2 est traduit d’un programme de John Ousterhout. I Programmer en Caml Avertissement a premi`ere partie de ce livre est une introduction progressive au langage Caml. On n’y suppose pas de connaissances pr´ealables autres que des notions ´el´ementaires de math´ematiques du niveau du lyc´ee. Les exemples de programmes que nous vous pr´esentons vont de l’exemple d’une ligne au vrai programme de plusieurs pages. Tous les exemples ont ´et´e mˆurement r´efl´echis pour ˆetre soit ´etonnants (voire amusants, pourquoi pas ?) soit r´eellement utiles ou repr´esentatifs des programmes qu’on ´ecrit vraiment. Si bien que nous esp´erons que tous pourront nous lire avec profit, du d´ebutant en programmation, ignorant compl`etement Caml et d´esirant s’en faire une id´ee, `a l’´etudiant confirm´e qui trouvera mati`ere `a r´eflexion dans des programmes non triviaux. En s’adressant `a un si vaste public, nous avons tent´e d’acc´el´erer la lecture de tous : le d´ebutant verra souvent des sections qu’on lui sugg`ere de ne pas lire, car elles sont compliqu´ees et pas indispensables pour la suite, tandis que le sp´ecialiste sera invit´e `a sauter des chapitres entiers si ses connaissances le lui permettent. Par exemple, le prochain chapitre d´ebute par un avertissement au sp´ecialiste : Si vous savez d´ej`a que « 2 + 2 ; ; » font « - : int = 4 », . . . , vous pouvez sauter ce chapitre. En revanche, le chapitre 3 contient une section «Effets et ´evaluation », qui s’ouvre par un avertissement au d´ebutant : «Cette section peut ˆetre saut´ee en premi`ere lecture. » La d´emarche que nous avons adopt´ee, c’est-`a-dire l’apprentissage par des exemples int´eressants, nous a conduits `a pr´esenter les notions du langage par n´ecessit´e : nous les expliquons lorsqu’elles interviennent et uniquement l`a. Il se peut donc que certaines notions, inutiles `a nos programmes, ne soient pas pass´ees en revue. Cela indique clairement qu’elles ne sont pas essentielles. Si l’on d´esire absolument une vue exhaustive des possibilit´es de Caml, on consultera le Manuel de r´ef´erence du langage Caml auquel nous avons d´ej`a fait allusion. 1 Premiers pas O`u l’on v´erifie que 2 et 2 font 4. i vous savez d´ej`a que « 2 + 2 ; ; » font « - : int = 4 » et que « let f = function x -> . . . » signifie « let f x = . . . », vous pouvez sauter ce chapitre. Sinon, il vous initiera `a l’interaction avec Caml. 1.1 Id´ees g´en´erales sur Caml Caml est un langage simple : il y a peu de constructions mais ces constructions sont les plus g´en´erales possibles. Caml utilise des notations intuitives ou consacr´ees par l’usage et souvent proches de celles des math´ematiques. Par exemple, pour ajouter 1 et 2, il suffit d’´ecrire 1 + 2. Et les chaˆınes de caract`eres, c’est-`a-dire les textes qui ne doivent pas ˆetre interpr´et´es par le langage, sont ´ecrites entre des guillemets ", notation classique en informatique. Bien que r´ealis´e en France, Caml est anglophone : ses mots-cl´es sont en anglais. Ainsi, les valeurs de v´erit´e de la logique math´ematique, le vrai et le faux, deviennent true et false en Caml. Ce n’est pas une r´eelle difficult´e, car les mots-cl´es sont peu nombreux et nous les traduirons au fur et `a mesure. Caml apporte une grande aide au programmeur, en s’effor¸cant de d´etecter le plus possible d’erreurs : le langage analyse les programmes qui lui sont soumis pour v´erifier leur coh´erence avant toute tentative de compilation ou d’ex´ecution. La principale analyse de coh´erence qu’il effectue se nomme le typage, m´ecanisme qui v´erifie que les op´erations qu’on utilise sont d´ej`a d´efinies et que les valeurs qu’on leur applique ont un sens. Par exemple, l’addition n’est d´efinie que pour les nombres, pas pour les valeurs de v´erit´e ni pour les chaˆınes de caract`eres. Donc true + 1 sera rejet´e, de la mˆeme fa¸con que 1 + "oui". Vous constaterez vite qu’il est ainsi plus difficile d’´ecrire en Caml des programmes manifestement faux : le langage les rejette automatiquement. Le corollaire est ´evidemment qu’il est plus facile d’´ecrire des programmes corrects ! Si vous ˆetes familier avec un langage algorithmique classique, comme Pascal par exemple, vous ne serez pas compl`etement d´epays´e par Caml : vous y retrouverez la notion de fonction et une notion similaire `a celle de proc´edure ; d’autre part nous avons 6 Premiers pas d´ej`a vu que Caml est un langage typ´e. Ces notions sont simplement g´en´eralis´ees et simplifi´ees : par exemple le typage est automatique et ne n´ecessite pas d’annotations dans les programmes comme c’est le cas en Pascal. 1.2 Dialoguer avec Caml Caml offre non seulement un compilateur traditionnel, qui transforme des fichiers de code source en code compil´e ex´ecutable par la machine, mais aussi un syst`eme interactif qui vous permet de dialoguer directement avec Caml, sans passer par l’interm´ediaire d’un fichier. Dans ce mode, le langage s’utilise comme une « calculette » : vous tapez des phrases au clavier de l’ordinateur et Caml r´eagit en vous donnant imm´ediatement les r´esultats de vos programmes. Nous utiliserons d’abord cette m´ethode d’interaction directe car elle facilite l’apprentissage. Nous verrons plus tard l’utilisation du compilateur ind´ependant, `a partir du chapitre 10. Vous pouvez donc entrer au terminal les exemples qui suivent, si vous avez d´ej`a install´e le syst`eme Caml Light sur votre machine. Toutes les phrases soumises `a Caml doivent ˆetre munies d’une indication de fin de phrase, ce qu’on note en Caml par ;; (deux points-virgules accol´es). C’est justifi´e pour un syst`eme qui offre une version interactive, dans la mesure o`u il est impossible de deviner quand l’utilisateur a termin´e sa phrase : par exemple apr`es 1 + 2, il est permis d’´ecrire encore + 3 pour ´evaluer 1 + 2 + 3. D’autre part, une phrase peut s’´etendre sur autant de lignes que n´ecessaire ; la fin de la ligne ne se confond donc pas avec la fin de la phrase. On devra donc ´ecrire ;; pour signaler la fin de la phrase. Il faut bien entendu ´egalement appuyer sur la touche « retour chariot » (return en anglais) comme c’est traditionnel pour toute interaction avec un ordinateur. En r´eponse au signe d’invite de Caml (le caract`ere # que Caml imprime pour indiquer qu’il attend que nous tapions quelque chose), demandons-lui d’effectuer un petit calcul : l’addition de 2 et de 2. Pour cela nous entrons simplement l’op´eration `a effectuer, 2 + 2, suivie de la marque de fin de phrase ;;. # 2 + 2;; - : int = 4 Caml nous r´epond imm´ediatement, en indiquant par un signe - que nous avons simplement calcul´e une valeur, que cette valeur est de type entier (: int) et qu’elle vaut 4 (= 4). Vous constatez que Caml a d´eduit tout seul le type du r´esultat du calcul. Pour un exemple si simple, ce n’est pas vraiment impressionnant, mais c’est un m´ecanisme absolument g´en´eral : quelle que soit la complexit´e du programme que vous lui soumettrez, Caml en d´eduira le type sans aucune intervention de votre part. 1.3 Les d´efinitions Vous pouvez donner un nom `a une valeur que vous calculez, pour ne pas perdre le r´esultat de votre calcul. La construction qui permet ainsi de nommer des valeurs s’appelle une d´efinition. Les d´efinitions 7 D´efinitions globales De mˆeme qu’en math´ematiques on ´ecrit : « soit s la somme des nombres 1, 2 et 3 », on ´ecrit en Caml (« soit » se traduit par let en anglais) : # let s = 1 + 2 + 3;; s : int = 6 Caml nous r´epond que nous avons d´efini un nouveau nom s, qui est de type entier (: int) et vaut 6 (= 6). Maintenant que le nom s est d´efini, il est utilisable dans d’autres calculs ; par exemple, pour d´efinir le carr´e de s, on ´ecrirait : # let s2 = s * s;; s2 : int = 36 Les d´efinitions sont des liaisons de noms `a des valeurs. On peut consid´erer ces noms (qu’on appelle aussi identificateurs, ou encore variables) comme de simples abr´eviations pour la valeur qui leur est li´ee. En particulier, une d´efinition n’est pas modifiable : un nom donn´e fait toujours r´ef´erence `a la mˆeme valeur, celle qu’on a calcul´ee lors de la d´efinition du nom. Le m´ecanisme du « let » est donc fondamentalement diff´erent du m´ecanisme d’affectation, que nous ´etudierons plus loin. Il est impossible de changer la valeur li´ee `a un nom; on peut seulement red´efinir ce nom par une nouvelle d´efinition, donc un nouveau « let ». Une fois d´efini, un nom a toujours la mˆeme valeur Informatique et math´ematiques La grande diff´erence entre les math´ematiques et les langages de programmation, mˆeme ceux qui se rapprochent des math´ematiques comme Caml, est qu’un langage de programmation calcule avec des valeurs et non pas avec des quantit´es formelles. Par exemple, en math´ematiques, si x est un entier, alors x − x est ´egal `a 0. Il n’est pas n´ecessaire de connaˆıtre la valeur de x pour obtenir le r´esultat du calcul ; on parle d’ailleurs plutˆot de simplification que de calcul. Au contraire, en Caml, on ne peut parler d’un nom s’il n’a pas ´et´e pr´ec´edemment d´efini. On ne peut donc pas calculer x − x si x n’est pas li´e `a une valeur pr´ecise, car il est alors impossible de faire effectivement la soustraction : # x - x;; Entr´ee interactive: >x - x;; >^ L’identificateur x n’est pas d´efini. Le langage indique ici que notre phrase est erron´ee puisque le nom x n’a jamais ´et´e d´efini ; on dit encore que x n’est pas « li´e ». Mais le r´esultat est ´evidemment le bon, d`es que l’identificateur est d´efini ; nous pouvons par exemple faire le calcul avec s : # s - s;; - : int = 0 Une autre diff´erence essentielle entre un programme et une d´efinition math´ematique r´eside dans la notion d’efficacit´e : un langage de programmation calcule des valeurs de 8 Premiers pas mani`ere effective, ce qui demande un certain temps. Si ce temps devient prohibitif, on peut consid´erer que le programme est incorrect, mˆeme si l’on peut prouver qu’il donnerait un jour le bon r´esultat. En math´ematiques, cette notion de temps de calcul est sans importance. Un autre ´ecueil majeur de l’informatique est qu’elle ne connaˆıt pas l’infini. Par exemple, la limite quand n tend vers l’infini de f(n), ce qu’on note en math´ematiques limn→∞ f(n) et qui signifie la valeur de f(n) quand n devient arbitrairement grand, existe ´eventuellement en math´ematiques, mais ne peut qu’ˆetre approch´ee par une machine. Enfin, l’´evaluation des expressions d’un langage de programmation tel que Caml ne termine pas toujours : les calculs peuvent « boucler » et donc ne jamais s’achever. Autrement dit, les fonctions d´efinissables par un programme sont en g´en´eral des fonctions partielles (non d´efinies pour certaines valeurs) plutˆot que des fonctions totales (toujours d´efinies). D´efinitions locales Les d´efinitions de noms que nous venons de voir sont permanentes : elles restent valides tant que vous n’abandonnez pas le syst`eme Caml. Ces d´efinitions « d´efinitives » sont qualifi´ees de globales. Cependant, pour faire un petit calcul, il est inutile d’utiliser des d´efinitions globales : on dispose donc en Caml d’un moyen de d´efinir temporairement des noms, pour la seule dur´ee du calcul en question. Ces d´efinitions temporaires sont les d´efinitions locales, qui disparaissent `a la fin de l’´evaluation de la phrase dans laquelle elles se trouvent. Ces d´efinitions locales ne sont donc plus valides apr`es le calcul de l’expression qui les suit (apr`es le mot-cl´e in, qui signifie « dans ») : # let s = 20 in s * 4;; - : int = 80 Le nom s a ´et´e li´e `a 20 pendant le calcul de s * 4, mais la d´efinition pr´ec´edente de s reste inchang´ee. Pour s’en rendre compte, il suffit de demander la valeur de s, c’est-`a-dire le r´esultat du calcul r´eduit `a s : # s;; - : int = 6 La d´efinition locale d’un nom est compl`etement ind´ependante du type actuel du nom: par exemple, s et s2 qui sont actuellement de type int peuvent ˆetre d´efinis localement avec le type string : # let s = "Le langage " and s2 = "Caml" in s ^ s2;; - : string = "Le langage Caml" Cet exemple utilise l’op´erateur ^ qui met deux chaˆınes de caract`eres bout `a bout (concat ´enation). Notez ´egalement que les d´efinitions multiples consistent en une simple succession de d´efinitions s´epar´ees par le mot-cl´e and (qui signifie « et »). 1.4 Fonctions Les fonctions forment les constituants ´el´ementaires des programmes en Caml. Un programme n’est rien d’autre qu’une collection de d´efinitions de fonctions, suivie d’un appel `a la fonction qui d´eclenche le calcul voulu. Fonctions 9 D´efinir une fonction D´efinir une fonction en Caml est simple et naturel, car la syntaxe est tr`es proche des notations math´ematiques usuelles. `A la d´efinition math´ematique « soit successeur la fonction d´efinie par successeur(x) = x+1 » correspond la d´efinition Caml suivante : # let successeur (x) = x + 1;; successeur : int -> int = Caml nous indique encore une fois que nous avons d´efini un nom: successeur. Ce nom a pour type int -> int (-> se prononce « fl`eche »), qui est le type des fonctions des entiers (int) vers les entiers (-> int) et ce nom a pour valeur une fonction (= ). Le syst`eme a trouv´e tout seul le type de la fonction, mais il ne sait pas comment imprimer les valeurs fonctionnelles, parce que leur repr´esentation interne est faite de code machine ; il affiche donc simplement sans plus de pr´ecisions. Effectivement, le nom successeur poss`ede maintenant une valeur : # successeur;; - : int -> int = Une d´efinition de fonction n’est donc pas essentiellement diff´erente d’une d´efinition d’entier ou de chaˆıne de caract`eres. Elle d´efinit simplement le nom de la fonction et lui donne une valeur qui est une fonction, ce qu’on appelle une valeur fonctionnelle. Application de fonctions L’application d’une fonction `a son argument suit aussi la convention math´ematique (rappelons que « f(x) » se prononce f de x) : # successeur (2);; - : int = 3 Le langage Caml fournit une syntaxe plus souple pour utiliser et d´efinir les fonctions : on peut supprimer les parenth`eses autour des noms des arguments des fonctions aussi bien au cours d’une d´efinition que lors d’une application. ´Etant donn´ee la paresse l´egendaire des programmeurs, c’est bien sˆur cette habitude qui pr´edomine ! Avec cette convention, on ´ecrit simplement # let successeur x = x + 1;; successeur : int -> int = # successeur 2;; - : int = 3 D´efinitions locale de fonctions Rien n’empˆeche de d´efinir une fonction localement, bien que cela surprenne souvent les d´ebutants en Caml. Voici un exemple de fonction locale : # let pr´ed´ecesseur x = x - 1 in (pr´ed´ecesseur 3) * (pr´ed´ecesseur 4);; - : int = 6 La fonction pr´ed´ecesseur n’est d´efinie que pendant le calcul du produit des pr´ed´ecesseurs de 3 et 4. 10 Premiers pas Les d´efinitions locales sont aussi utilis´ees dans les d´efinitions globales, par exemple pour calculer la formule qui d´efinit une fonction (ce qu’on appelle le corps de la fonction). D´efinissons par exemple la fonction pr´ed´ecesseur_carr´e qui retourne le carr´e du pr´ed´ecesseur d’un nombre (la fonction x 7→ (x − 1)2). Nous d´efinissons localement le pr´ed´ecesseur de l’argument, puis l’´elevons au carr´e : # let pr´ed´ecesseur_carr´e x = let pr´ed´ecesseur_de_x = x - 1 in pr´ed´ecesseur_de_x * pr´ed´ecesseur_de_x;; pr´ed´ecesseur_carr´e : int -> int = # pr´ed´ecesseur_carr´e 3;; - : int = 4 Une fonction peut aussi d´efinir localement une autre fonction. Par exemple, pour d´efinir la fonction puissance4 qui ´el`eve son argument `a la puissance quatre, il est naturel d’utiliser la formule x4 = (x2)2, donc d’´elever au carr´e le carr´e de l’argument. Pour cela, on d´efinit localement la fonction carr´e et on l’utilise deux fois : # let puissance4 x = let carr´e y = y * y in (* d´efinition locale d’une fonction *) carr´e (carr´e x);; puissance4 : int -> int = # puissance4 3;; - : int = 81 Comme on le voit sur cet exemple, les commentaires en Caml sont encadr´es entre (* et *). Ils peuvent contenir n’importe quel texte, y compris d’autres commentaires, et s’´etendre sur plusieurs lignes. Fonctions `a plusieurs arguments Les fonctions poss´edant plusieurs arguments ont simplement plusieurs noms d’arguments dans leur d´efinition : # let moyenne a b = (a + b) / 2;; moyenne : int -> int -> int = # let p´erim`etre_du_rectangle longueur largeur = 2 * (longueur + largeur);; p´erim`etre_du_rectangle : int -> int -> int = Le type de ces deux fonctions, int -> int -> int, indique qu’elles prennent deux arguments de type int (int -> int ->) et calculent un entier (-> int). Lorsque des fonctions ont plusieurs arguments, il faut ´evidemment leur fournir aussi leur compte d’arguments quand on les applique. Ainsi, un appel `a p´erim`etre_du_rectangle ou moyenne comportera deux arguments : # p´erim`etre_du_rectangle 3 2;; - : int = 10 # moyenne 5 3;; - : int = 4 Fonctions 11 Fonctions anonymes Une fonction Caml est un « citoyen `a part enti`ere », on dit aussi « citoyen de premi`ere classe », c’est-`a-dire une valeur comme toutes les autres. Une fonction a le mˆeme statut qu’un nombre entier : elle est calcul´ee, on peut la passer en argument ou la retourner en r´esultat. Les valeurs fonctionnelles sont cr´e´ees lors des d´efinitions de fonctions, comme nous venons de le voir. Cependant, on peut aussi construire des valeurs fonctionnelles sans leur donner de nom, en utilisant des fonctions anonymes. Ces fonctions sont introduites par le mot-cl´e function, suivi de la formule qui les d´efinit : # (function x -> 2 * x + 1);; - : int -> int = Encore une fois, Caml nous indique par le symbole - que nous avons fait un simple calcul, dont le r´esultat est de type int -> int et dont la valeur est une fonction (= ). On applique les fonctions anonymes comme toutes les autres fonctions, en les faisant suivre de leur(s) argument(s) : # (function x -> 2 * x + 1) (2);; - : int = 5 D´efinition de fonctions `a l’aide de fonctions anonymes Il existe un autre style de d´efinitions math´ematiques de fonctions : « Soit successeur : Z → Z x 7→ x + 1 » Ce style insiste sur le fait que successeur est une fonction qui `a tout ´el´ement x de l’ensemble Z des entiers associe x+1. `A l’aide des fonctions anonymes, cette d´efinition se traduit tr`es simplement en Caml : # let successeur = function x -> x + 1;; successeur : int -> int = Contraintes de type Pour se rapprocher encore du style de la d´efinition math´ematique, on peut mˆeme ajouter une contrainte de type sur le nom de la fonction, qui rend compte de l’indication successeur : Z → Z des math´ematiques. Une contrainte de type (ou annotation de type) est une indication explicite du type d’une expression Caml. Vous pouvez, si vous le souhaitez, ajouter des annotations de type dans vos programmes, par exemple pour aider `a la relecture. Pour annoter un morceau de programme avec un type, il suffit de mettre ce morceau de programme entre parenth`eses avec son type, avec la mˆeme convention que le syst`eme interactif, c’est-`a-dire un « : » suivi d’un nom de type : # ("Caml" : string);; - : string = "Caml" Nous obtenons maintenant une d´efinition de la fonction successeur tr`es fid`ele `a celle des math´ematiques : 12 Premiers pas # let (successeur : int -> int) = function x -> x + 1;; successeur : int -> int = Ce style revient `a d´efinir le nom successeur comme un nom ordinaire, mais dont la valeur est une fonction. Cette d´efinition est absolument ´equivalente `a la pr´ec´edente d´efinition de successeur : let successeur (x) = x + 1;; Fonctions anonymes `a plusieurs arguments Le choix entre les deux modes de d´efinition des fonctions est donc, comme en math´ematiques, une simple affaire de style. En r`egle g´en´erale, le style « let successeur (x) = » est plus concis, particuli`erement lorsque la fonction a plusieurs arguments, puisque l’autre style oblige `a introduire chacun des arguments par une construction « function argument -> ». Par exemple, d´efinir la fonction moyenne dans le style « function x -> » conduirait `a ´ecrire : # let moyenne = function a -> function b -> (a + b) / 2;; moyenne : int -> int -> int = Au passage, nous remarquons qu’une fonction anonyme a parfaitement le droit d’avoir plusieurs arguments. Attention : il n’est pas permis d’´ecrire function a b ->, il faut imp´erativement r´ep´eter le mot-cl´e function, une fois par argument. C’est pourquoi nous utiliserons la plupart du temps le style le plus l´eger, celui qui ´evite d’employer le mot function. Les tests et l’alternative Caml fournit une construction pour faire des calculs qui d´ependent d’une condition : c’est l’alternative, le classique « if . . . then . . . else . . . ». Cette construction correspond au calcul « si condition alors expression1 sinon expression2 », qui signifie simplement qu’il faut calculer expression1 si la condition est vraie et expression2 sinon. Nous illustrons cette construction en impl´ementant (c’est-`a-dire en r´ealisant sur machine) la fonction « valeur absolue », qui calcule la valeur d’un nombre ind´ependamment de son signe. Cette fonction, not´ee |x| en math´ematiques, est d´efinie comme : |x| = ( x si x ≥ 0 −x sinon Sachant qu’en Caml les comparaisons entre nombres entiers suivent les notations math´ematiques (<, >, =, >= et <=), nous sommes arm´es pour d´efinir la fonction valeur absolue : # let valeur_absolue (x) = if x >= 0 then x else -x;; valeur_absolue : int -> int = # valeur_absolue (3);; - : int = 3 # valeur_absolue (-3);; - : int = 3 Valeurs et programmes 13 Valeurs de v´erit´e Remarquons que les tests calculent un r´esultat, une valeur de v´erit´e. Une valeur de v´erit´e est soit « vrai », soit « faux », ce qui se note true et false en Caml. On appelle aussi les valeurs de v´erit´e « valeurs bool´eennes », en l’honneur du logicien Boole ; elles sont du type bool. On peut donc employer les tests pour calculer un bool´een : # 2 < 1;; - : bool = false # (valeur_absolue (3)) = (valeur_absolue (-3));; - : bool = true 1.5 Valeurs et programmes Nous venons de faire des calculs. Mais o`u sont donc les programmes ? Ce sont tout simplement les fonctions ! Un programme consiste en une d´efinition de fonction qui calcule le r´esultat d´esir´e. En g´en´eral cette fonction utilise `a son tour d’autres fonctions, qui correspondent `a la notion de sous-programmes. Par exemple, si vous d´esirez calculer la somme des carr´es de deux nombres, vous d´efinirez d’abord la fonction carr´e : # let carr´e (x) = x * x;; carr´e : int -> int = pour ensuite d´efinir la fonction d´esir´ee : # let somme_des_carr´es x y = carr´e (x) + carr´e (y);; somme_des_carr´es : int -> int -> int = et enfin l’appliquer dans le cas qui vous int´eresse : # somme_des_carr´es 3 4;; - : int = 25 En r´esum´e : une fonction manipule des valeurs (entiers, chaˆınes de caract`eres, bool´eens) qui ont toutes un type ; la fonction elle-mˆeme est une valeur et poss`ede donc un type. En ce sens, les programmes en Caml sont des valeurs ! 1.6 Impression Notion d’effet Caml propose bien sˆur le moyen d’imprimer des valeurs `a l’´ecran ou dans des fichiers. On utilise pour cela des fonctions dont le but n’est pas d’effectuer des calculs mais de produire des effets, c’est-`a-dire une action sur le monde ext´erieur, par exemple une interaction avec l’utilisateur du programme, l’´ecriture d’un fichier ou d’un message au terminal. Un premier effet Nous allons r´ealiser un premier effet tr`es simple : nous ´ecrivons «Bonjour ! » `a l’´ecran en utilisant la fonction pr´ed´efinie print_string qui a justement pour effet d’imprimer son argument au terminal. Une fonction pr´ed´efinie est une fonction qui 14 Premiers pas vous est fournie par le syst`eme Caml ; vous n’avez donc pas besoin de l’´ecrire. Ces fonctions sont d´ecrites en d´etails dans le Manuel de r´ef´erence du langage Caml. Elles sont aussi appel´ees « fonctions primitives » ou tout simplement « primitives ». Essayons la primitive print_string : # print_string "Bonjour!";; Bonjour!- : unit = () L’impression s’est produite comme pr´evu. Cependant Caml nous indique aussi que nous avons calcul´e un r´esultat de type unit et qui vaut (). Le type unit est un type pr´ed´efini qui ne contient qu’un seul ´el´ement, « () », qui signifie par convention « rien ». Nous n’avons pas demand´e ce r´esultat : tout ce que nous voulions, c’est faire une impression (un effet). Mais toutes les fonctions Caml doivent avoir un argument et rendre un r´esultat. Lorsqu’une fonction op`ere uniquement par effets, on dit que cette fonction est une proc´edure On utilise alors « rien », c’est-`a-dire (), en guise de r´esultat ou d’argument. (En position d’argument dans une d´efinition de fonction, on peut consid´erer () comme un argument minimal : l’argument (x) auquel on aurait mˆeme retir´e la variable x ; de mˆeme en r´esultat, () figure une expression parenth´es´ee dont tout le texte aurait disparu.) Impressions successives : s´equencement Supposez qu’il nous faille imprimer deux textes successifs `a l’´ecran : par exemple, «Bonjour » puis « tout le monde ! ». Nous devons faire deux effets `a la suite l’un de l’autre, en s´equence. ´Evaluer en s´equence deux expressions e1 et e2 signifie simplement les ´evaluer successivement : d’abord e1, puis e2. Comme dans la plupart des langages de programmation, la s´equence est not´ee par un point virgule en Caml. L’op´eration « e1 puis e2 » s’´ecrit donc e1 ; e2. Nous ´ecrivons donc : # print_string "Bonjour "; print_string "tout le monde!";; Bonjour tout le monde!- : unit = () La machine a d’abord imprim´e Bonjour puis tout le monde!, comme on s’y attendait. Le r´esultat de toute l’op´eration (de toute la s´equence) est « rien ». Cela s’explique naturellement parce que le r´esultat de la premi`ere impression (un premier « rien ») a ´et´e oubli´e. De mani`ere g´en´erale, la s´equence « jette » le r´esultat du premier calcul et renvoie le r´esultat du second : e1 ; e2 s’´evalue en la mˆeme valeur que e2. Comme le r´esultat de e1 est d´etruit, il est clair que l’expression e1 n’est utile que si elle produit des effets : il serait stupide d’´evaluer la s´equence (1 + 2); 0 qui rendrait exactement le mˆeme r´esultat que 0. # (1 + 2); 0;; Entr´ee interactive: >(1 + 2); 0;; > ^^^^^ Attention: cette expression est de type int, mais est utilis´ee avec le type unit. - : int = 0 On constate d’ailleurs que le compilateur ´emet une alerte pour indiquer que l’expression (1 + 2) produit un r´esultat qui sera ignor´e ! Conventions syntaxiques 15 Pour d´elimiter pr´ecis´ement une s´equence, on l’encadre souvent entre les mots-cl´es begin (d´ebut) et end (fin) : # begin print_string "Voil`a "; print_string "Caml!"; print_newline () end;; Voil`a Caml! - : unit = () La derni`ere expression, print_newline (), fait imprimer un retour chariot. La fonction print_newline op`ere enti`erement par effets, elle n’a donc pas de param`etre significatif, ni de r´esultat significatif. 1.7 Conventions syntaxiques R´esumons quelques conventions syntaxiques qu’il est bon d’avoir en tˆete pour la suite. D´efinitions de fonctions Pour les d´efinitions de fonctions, nous avons la convention suivante : let f x = . . . est ´equivalent `a let f = function x -> . . . On peut it´erer cette convention pour d´efinir les fonctions `a plusieurs arguments : let f x y = . . . est ´equivalent `a let f = function x -> function y -> . . . Application de fonctions Pour ce qui est de l’application de fonction, nous avons vu que les parenth`eses autour de l’argument ´etait facultatives : Si x est une variable ou une constante, f x est ´equivalent `a f (x) Attention : cette convention n’est valable que lorsque x est une variable ou une constante. Si vous employez cette convention avec une expression plus compliqu´ee, les parenth`eses retrouvent leur sens habituel en math´ematiques (le groupement des expressions) et la pr´esence de parenth`eses modifie alors le r´esultat du calcul. Par exemple, l’argument n´egatif d’une fonction doit imp´erativement ˆetre parenth´es´e : f (−1) sans parenth`eses est compris comme la soustraction f − 1. De mˆeme, si l’argument est une expression complexe, vous ne pouvez pas supprimer les parenth`eses sans changer le r´esultat : # successeur (2 * 3);; - : int = 7 16 Premiers pas # successeur 2 * 3;; - : int = 9 Cette derni`ere phrase est comprise par Caml comme si vous aviez ´ecrit (successeur 2) * 3. C’est d’ailleurs un ph´enom`ene g´en´eral pour tous les op´erateurs de Caml : les applications de fonctions en argument des op´erations sont implicitement parenth´es´ees. Par exemple successeur 2 - successeur 3 est lu comme (successeur 2) - (successeur 3), et de mˆeme pour tous les op´erateurs : successeur 2 >= successeur 3 est correctement interpr´et´e. f x + g y est ´equivalent `a (f x) + (g y) Application de fonctions `a plusieurs arguments L’application de fonctions `a plusieurs arguments suit les mˆemes conventions : moyenne (2) (6) est ´equivalent `a moyenne 2 6, mais vous devez conserver les parenth`eses si vous voulez calculer moyenne (2 * 3) (3 + 3). Techniquement, on dit que l’application « associe `a gauche » en Caml, ce qui signifie que les parenth`eses peuvent ˆetre omises dans (f x) y, qui correspond au r´esultat de f de x appliqu´e `a y, mais qu’elles sont indispensables dans f (g x), qui signifie au contraire f appliqu´ee au r´esultat de l’application de la fonction g `a x. f x y est ´equivalent `a (f x) y Au lieu de moyenne 2 6, on peut donc ´ecrire (moyenne 2) 6 . La deuxi`eme forme est ´evidemment un peu ´etrange, mais elle a la mˆeme signification que la premi`ere. On a donc beaucoup de mani`eres ´equivalentes d’exprimer l’application de la fonction moyenne `a 2 et `a 6. La plus simple est ´evidemment sans parenth`eses aucunes : « moyenne 2 6 ». Mais l’on peut ´ecrire aussi « moyenne (2) (6) » ou, en utilisant la r`egle pr´ec´edente pour ajouter encore des parenth`eses, « (moyenne (2)) 6 » ou mˆeme « (moyenne (2)) (6) ». En pratique, nous utiliserons toujours la forme la plus simple, sans parenth`eses. En revanche, on ne peut absolument pas grouper les arguments 2 et 6 `a l’int´erieur de parenth`eses : moyenne (2 6) est erron´e. Cela signifierait en effet qu’on d´esire appliquer moyenne `a un seul argument « 2 6 ». Qui plus est, cela voudrait dire qu’on tente d’appliquer le nombre 2 au nombre 6 ! Des expressions construites sur le mod`ele moyenne (2 6), c’est-`a-dire, plus g´en´eralement, du genre f (g y), ont pourtant un sens. Consid´erez, par exemple le calcul du successeur du successeur de 1. On ´ecrit naturellement : # successeur (successeur 1);; - : int = 3 Mais si l’on ˆote les parenth`eses, on ´ecrit successeur successeur 1 et cela signifie maintenant que nous voulons appliquer la fonction successeur `a deux arguments (le premier argument serait la fonction successeur elle-mˆeme et le second argument serait 1). Cependant la fonction successeur n’admet qu’un seul argument ; si nous retirons les parenth`eses (sciemment ou par inadvertance), Caml nous indique donc une erreur : Diagrammes syntaxiques 17 # successeur successeur 1;; Entr´ee interactive: >successeur successeur 1;; > ^^^^^^^^^^ Cette expression est de type int -> int, mais est utilis´ee avec le type int. Le message indique en effet que l’expression soulign´ee (successeur) est une fonction de type int -> int : elle ne peut pas ˆetre utilis´ee comme un argument entier. Retenons de toute fa¸con que : f (g y) n’est pas ´equivalent `a f g y 1.8 Diagrammes syntaxiques Nous r´esumons la mani`ere d’´ecrire les constructions de Caml au moyen de d´efinitions simplifi´ees de syntaxe, telles que : expression ::= entier | chaˆıne-de-caract`eres | bool´een | . . . Cette d´efinition signifie qu’une expression du langage Caml (expression) est (::=) ou bien un entier (entier), ou bien (|) une chaˆıne de caract`eres (chaˆıne-de-caract`eres), ou bien (|) un bool´een (bool´een), ou bien . . . Ceci n’est qu’un exemple et nous ne faisons ´evidemment pas figurer toutes les constructions concernant les expressions. D’ailleurs, nous savons d´ej`a qu’une s´equence est une expression ; elle devrait donc figurer dans l’ensemble des expressions, de mˆeme que les expressions parenth´es´ees (toute expression entour´ee de parenth`eses est une expression). Avec ce formalisme, une s´equence se d´ecrit par : s´equence ::= expression ; expression ou encore, si elle est d´elimit´ee par les mots-cl´es begin et end : s´equence ::= begin expression ; expression end Cette m´ethode de description de la syntaxe d’un langage est appel´ee « syntaxe BNF », pour Backus-Naur Form, des noms de John Backus et Peter Naur qui l’ont r´epandue. l 2 R´ecursivit´e O`u l’on apprend `a parler de ce qu’on ne connaˆıt pas encore . . . ous ne connaissez pas les fonctions r´ecursives, ou n’y avez jamais rien compris, ou bien vous vous passionnez pour les tours de Hanoi ? Alors lisez ce chapitre, il est fait pour vous. Nous y apprendrons une nouvelle mani`ere d’utiliser l’ordinateur. Jusqu’`a pr´esent nous nous en servions pour obtenir les solutions `a des probl`emes que nous savions r´esoudre, mais qui demandaient trop de calculs math´ematiques pour que nous les traitions `a la main. C’est l’emploi de l’ordinateur le plus r´epandu actuellement : la machine sert `a effectuer un tr`es grand nombre de fois des op´erations simples (pensez `a des logiciels de gestion, paye ou comptes en banque). Avec la r´ecursivit´e, on ´el`eve la programmation `a un rang autrement plus noble : on ´ecrit des programmes qui r´esolvent des probl`emes que l’on ne sait pas forc´ement r´esoudre soi-mˆeme. Parvenir `a diriger une machine pour qu’elle trouve pour nous la solution d’un probl`eme est r´eellement fascinant, il est mˆeme grisant de voir apparaˆıtre cette solution au terminal, quand elle est le r´esultat de l’ex´ecution d’un programme qu’on a soi-mˆeme ´ecrit sans avoir conscience de savoir r´esoudre le probl`eme. 2.1 Fonctions r´ecursives simples Notion de r´ecursivit´e Une d´efinition r´ecursive est une d´efinition dans laquelle intervient le nom qu’on est en train de d´efinir. Cela correspond dans le langage courant `a une phrase qui « se mord la queue ». L’exemple typique est la r´eponse `a la question «Qu’est-ce qu’un ´ego¨ıste ? » : «Quelqu’un qui ne pense pas `a moi ! ». Il est clair qu’on soup¸conne l´egitimement ce genre de phrases d’ˆetre d´enu´ees de sens et que c’est souvent le cas. Pourtant, les d´efinitions r´ecursives sont tr`es employ´ees en math´ematiques et en informatique, domaines o`u l’on se m´efie beaucoup des phrases « d´enu´ees de sens ». Il existe donc forc´ement des phrases qui « se mordent la queue » et pourtant poss`edent une signification pr´ecise, utilisable en math´ematiques ou informatique. Toute la difficult´e des d´efinitions r´ecursives provient de la d´etection des cas o`u la r´ecursivit´e entraˆıne le non-sens. Une d´efinition r´ecursive sens´ee est qualifi´ee de bien fond´ee. Nous verrons par l’exemple ce que cela veut dire. 20 R´ecursivit´e L’intuition la plus simple qu’on puisse donner de la r´ecursivit´e est l’id´ee de « recommencer » la mˆeme chose. La r´ecursivit´e est pr´esente aussi dans le domaine graphique, un exemple nous est donn´e par le c´el`ebre dessin qui orne les couvercles de «Vache qui rit », figurant une vache qui porte en boucles d’oreilles des boˆıtes de «Vache qui rit » (dont les couvercles comportent donc le dessin lui-mˆeme). Dans le domaine physique, l’infinit´e d’images qui apparaˆıt dans deux miroirs quasi parall`eles est aussi une bonne analogie (exp´erience habituelle chez le coiffeur). Prenons un exemple plus informatique : la tr`es c´el`ebre fonction « factorielle », qui retourne le produit des nombres entiers inf´erieurs ou ´egaux `a son argument. En math´ematiques, elle est not´ee par un point d’exclamation (!) plac´e apr`es son argument. On a par exemple 4! = 4 × 3 × 2 × 1. La fonction factorielle est d´efinie par : n! = ( 1 si n = 0 n × (n − 1)! sinon. Cette d´efinition est r´ecursive : le nom « ! » intervient dans le corps de sa propre d´efinition. Donc, pour comprendre la signification de n! il faut comprendre celle de (n − 1)!. Cela semble difficile, car on exige la compr´ehension d’une notion qu’on est justement en train d’expliquer . . . En fait, cela se justifie parce que le calcul de n! termine toujours : il suffit d’ˆetre patient et de continuer le calcul jusqu’`a atteindre 0, ce qui arrivera forc´ement puisqu’on explique la signification de n! en fonction de (n−1)!. Par exemple : 3! = 3 × (3 − 1)! = 3 × 2! = 3 × 2 × (2 − 1)! = 3 × 2 × 1! = 3 × 2 × 1 × (1 − 1)! = 3 × 2 × 1 × 0! = 3 × 2 × 1 × 1 = 6 La d´efinition math´ematique r´ecursive de la fonction factorielle est donc bien fond´ee : on obtient finalement un r´esultat pour tout argument entier naturel. Ce genre de d´efinition se traduit tr`es facilement en Caml ; mais pour cela nous devons explicitement pr´evenir Caml que nous d´esirons faire une d´efinition r´ecursive, grˆace `a une nouvelle construction : let rec. Avant de l’employer, expliquons pourquoi il est n´ecessaire d’introduire une nouvelle construction. Port´ee statique et d´efinitions r´ecursives En math´ematiques, vous ne pouvez parler d’une entit´e math´ematique avant d’en avoir donn´e la d´efinition. Le mˆeme axiome est vrai en Caml : vous ne pouvez utiliser un identificateur s’il n’a re¸cu une d´efinition pr´ealable. C’est ce qu’on appelle la port´ee statique, parce que vous pouvez trouver la d´efinition de n’importe quel identificateur ind´ependamment du comportement du programme `a l’ex´ecution (au contraire de la port´ee dynamique, o`u la valeur d’un identificateur d´epend de la fa¸con dont le calcul se d´eroule). En Caml, pour une occurrence quelconque d’un identificateur, disons x, il vous suffit d’examiner le texte du programme qui pr´ec`ede x pour trouver la liaison qui a d´efini x. Cette liaison est soit une d´efinition de x : let x =, soit une d´efinition de fonction qui a x pour param`etre : function x ->. Fonctions r´ecursives simples 21 # let x = 1 in let x = x + 1 in x + 3;; - : int = 5 L’identificateur x qui apparaˆıt dans x + 1 dans la d´efinition let x = x + 1 est li´e `a 1 (par la pr´ec´edente d´efinition let x = 1), alors que le dernier x dans x + 3 est li´e `a 2 par la pr´ec´edente d´efinition let x = x + 1, comme le sugg`ere ce sch´ema : let x = 1 in let x = x + 1 in x + 3 i 6 i 6 Le m´ecanisme de liaison est similaire pour des d´efinitions simultan´ees (d´efinitions s´epar´ees par le mot-cl´e and). # let x = 1 in let x = x + 1 and y = x + 2 in x + y;; - : int = 5 Les deux x intervenant dans les d´efinitions let x = ... and y = ... font tous les deux r´ef´erence au nom x pr´ec´edemment d´efini par let x = 1. Les liaisons sont mises en ´evidence dans ce sch´ema : let x = 1 in let x = x + 1 and y = x + 2 in x + y i i 6 i 6 i 6 On retiendra que, dans une d´efinition Caml (y compris une d´efinition simultan´ee), Un nom fait toujours r´ef´erence `a une d´efinition pr´ealable. La construction let rec Ceci pose ´evidemment probl`eme pour d´efinir des fonctions r´ecursives : nous ne pouvons utiliser une d´efinition introduite par un let, `a cause de la r`egle de port´ee statique. En effet, si nous ´ecrivons let f = ... f ..., l’occurrence de f dans l’expression d´efinissante ... f ... ne correspond pas au nom f que nous sommes en train de d´efinir (en particulier parce que f n’est pas encore d´efinie !) mais doit correspondre `a une d´efinition pr´ec´edente de f. Un petit sch´ema vaut mieux qu’un long discours : la liaison de f s’´etablit vers le pass´e. let f = ... fi...in 6 C’est pourquoi une simple construction let ne permet pas de d´efinir une fonction r´ecursive : 22 R´ecursivit´e # let factorielle n = if n = 0 then 1 else n * factorielle (n - 1);; Entr´ee interactive: >let factorielle n = if n = 0 then 1 else n * factorielle (n - 1);; > ^^^^^^^^^^^ L’identificateur factorielle n’est pas d´efini. En bref : une liaison let n’est pas r´ecursive ; il y a donc en Caml une construction sp´eciale, let rec, destin´ee `a introduire les d´efinitions r´ecursives. Cette construction ´etablit une liaison de la forme suivante : let rec f = ... fi...in ? Maintenant, nous d´efinissons facilement la fonction factorielle : # let rec factorielle n = if n = 0 then 1 else n * factorielle (n - 1);; factorielle : int -> int = # factorielle 3;; - : int = 6 Compter `a l’endroit et `a l’envers Pour comprendre comment s’ex´ecute un appel `a une fonction r´ecursive, d´efinissons une fonction qui ´enum`ere les nombres par ordre d´ecroissant jusqu’`a 1, `a partir d’une certaine limite : par exemple, pour une limite de 5 nous voulons obtenir l’impression de « 5 4 3 2 1 » sur l’´ecran. Bien que le but de ce programme soit exclusivement de produire des effets, la r´ecursivit´e s’y introduit naturellement, puisque ´enum´erer `a partir d’une certaine limite n, c’est : si la limite est 0, alors ne rien faire ; sinon, imprimer n, puis ´enum´erer les nombres pr´ec´edents. Si l’on se rend compte que « ´enum´erer les nombres pr´ec´edents » consiste tout simplement `a appeler notre fonction avec la nouvelle limite n − 1, on obtient le programme suivant : # let rec compte_`a_rebours n = if n = 0 then () else begin print_int n; print_string " "; compte_`a_rebours (n - 1) end;; compte_`a_rebours : int -> unit = # compte_`a_rebours 10;; 10 9 8 7 6 5 4 3 2 1 - : unit = () La fonction d’impression des entiers au terminal se nomme tout naturellement print_int, par analogie avec la fonction d’impression des chaˆınes print_string. Vous devinez le sch´ema de nommage de ces fonctions, qui consiste `a suffixer le nom print_ par le type de l’argument. Ainsi, la fonction d’impression des nombres flottants (les valeurs du type float que nous verrons au chapitre 8) s’appelle print_float, et celle pour les caract`eres (type char) s’appelle print_char. Telle qu’elle est ´ecrite, la fonction compte_`a_rebours ne termine pas si on lui passe un argument n´egatif (la d´efinition n’est donc pas bien fond´ee). Il serait plus sˆur de remplacer le test n = 0 par le test n <= 0. Fonctions r´ecursives simples 23 Par curiosit´e, inversons l’appel r´ecursif et les impressions : autrement dit rempla ¸cons print_int n; print_string " "; compte_`a_rebours (n - 1) par compte_`a_rebours (n - 1); print_int n; print_string " ". Et afin de ne pas perdre notre fonction pr´ec´edente, nous nommons compte cette version modifi´ee de compte_`a_rebours. Nous obtenons : # let rec compte n = if n = 0 then () else begin compte (n - 1); print_int n; print_string " " end;; compte : int -> unit = # compte 10;; 1 2 3 4 5 6 7 8 9 10 - : unit = () Cette fonction compte `a l’endroit ! C’est plus difficile `a comprendre : l’impression se produit au retour des appels r´ecursifs. On doit d’abord atteindre n = 0 avant d’imprimer le premier nombre, qui est alors 1. En effet, c’est toujours pendant l’´evaluation de compte 1 que nous appelons compte 0. Apr`es avoir imprim´e 1, compte 1 retourne `a l’´evaluation de compte 2, qui ´ecrit 2 et retourne `a compte 3, et ainsi de suite. Nous allons utiliser le m´ecanisme de « trace » de Caml pour suivre les appels r´ecursifs et les impressions. Ce m´ecanisme imprime `a l’´ecran les appels successifs d’une fonction, ainsi que les r´esultats que la fonction calcule. Tra¸cons par exemple la fonction successeur : # let successeur x = x + 1;; successeur : int -> int = # trace "successeur";; La fonction successeur est dor´enavant trac´ee. - : unit = () # successeur 2;; successeur <-- 2 successeur --> 3 - : int = 3 Vous l’avez devin´e, l’appel d’une fonction est indiqu´e par le signe <-- suivi de l’argument concern´e, tandis que le signe --> signale un retour de fonction et affiche le r´esultat obtenu. Nous suivons maintenant le d´eroulement des appels `a compte et compte_`a_rebours : # trace "compte"; trace "compte_`a_rebours";; La fonction compte est dor´enavant trac´ee. La fonction compte_`a_rebours est dor´enavant trac´ee. - : unit = () # compte 3;; compte <-- 3 compte <-- 2 compte <-- 1 compte <-- 0 compte --> () 1 compte --> () 24 R´ecursivit´e 2 compte --> () 3 compte --> () - : unit = () On voit clairement que compte i s’ach`eve avant l’impression de i−1 et que compte 0 se termine avant toute impression. Cela contraste avec compte_`a_rebours, qui imprime i avant l’appel compte_`a_rebours (i − 1) : # compte_`a_rebours 3;; compte_`a_rebours <-- 3 3 compte_`a_rebours <-- 2 2 compte_`a_rebours <-- 1 1 compte_`a_rebours <-- 0 compte_`a_rebours --> () compte_`a_rebours --> () compte_`a_rebours --> () compte_`a_rebours --> () - : unit = () ´E peler `a l’endroit et `a l’envers Nous allons maintenant montrer la r´ecursion `a l’oeuvre sur les chaˆınes de caract`eres. Pour ce faire, nous avons besoin d’op´erations suppl´ementaires sur les chaˆınes de caract` eres. La fonction pr´ed´efinie string_length renvoie la longueur d’une chaˆıne de caract` eres. La notation s.[i] d´esigne le ii`eme caract`ere de la chaˆıne de caract`eres s. Le premier caract`ere a pour num´ero 0 ; le dernier a donc pour num´ero string_length s−1. Acc`es dans une chaˆıne s ::= s .[ indice ] # let le_langage = "Caml";; le_langage : string = "Caml" # string_length le_langage;; - : int = 4 # le_langage.[0];; - : char = ‘C‘ Un caract`ere en Caml est donc un signe typographique quelconque compris entre deux symboles ‘. Voici deux fonctions qui ´epellent des mots. La premi`ere ´epelle `a l’envers, en commen ¸cant par le dernier caract`ere de la chaˆıne et en s’appelant r´ecursivement sur le caract`ere pr´ec´edent. # let rec ´epelle_envers_aux s i = if i >= 0 then begin print_char s.[i]; print_char ‘ ‘; ´epelle_envers_aux s (i - 1) end;; ´epelle_envers_aux : string -> int -> unit = # let ´epelle_envers s = ´epelle_envers_aux s (string_length s - 1);; ´epelle_envers : string -> unit = Fonctions r´ecursives simples 25 # ´epelle_envers "snob";; b o n s - : unit = () La seconde ´epelle `a l’endroit, en commen¸cant par le premier caract`ere et en s’appelant r´ecursivement sur le prochain caract`ere. # let rec ´epelle_aux s i = if i < string_length s then begin print_char s.[i]; print_char ‘ ‘; ´epelle_aux s (i + 1) end;; ´epelle_aux : string -> int -> unit = # let ´epelle s = ´epelle_aux s 0;; ´epelle : string -> unit = # ´epelle "snob";; s n o b - : unit = () Ces deux exemples utilisent une forme nouvelle de l’alternative : la construction « if . . . then . . . » sans partie else. La partie else omise est implicitement compl´et´ee par le compilateur, qui ajoute else (), autrement dit « sinon rien ». Ainsi, le code : if i >= 0 then begin ... end;; est compris par Caml comme si nous avions ´ecrit : if i >= 0 then begin ... end else ();; Cette compl´etion automatique vous explique pourquoi la phrase suivante est mal typ´ee : # if true then 1;; Entr´ee interactive: >if true then 1;; > ^ Cette expression est de type int, mais est utilis´ee avec le type unit. Retenons la d´efinition d’une alternative sans partie else : if cond then e est ´equivalent `a if cond then e else () Les palindromes Un palindrome est un mot (ou un groupe de mots sans blancs) qui se lit aussi bien `a l’endroit qu’`a l’envers. Pour d´eterminer si un mot constitue un palindrome, il suffit de v´erifier que ses premier et dernier caract`eres sont identiques, puis de v´erifier que le mot situ´e entre ces deux caract`eres est lui aussi un palindrome. chaˆıne s c mˆemes caract`eres c palindrome s’ Il faut aussi remarquer qu’un mot r´eduit `a un seul caract`ere est un palindrome et que la chaˆıne vide est elle aussi un palindrome (puisqu’elle se lit aussi bien `a l’endroit qu’`a 26 R´ecursivit´e l’envers). Pour obtenir une sous-chaˆıne d’une chaˆıne de caract`eres, on utilise la fonction pr´ed´efinie sub_string (sous-chaˆıne), qui extrait une sous-chaˆıne partant d’un indice donn´e et d’une longueur donn´ee : # sub_string "Le langage Caml" 3 7;; - : string = "langage" En particulier, sub_string s 1 (string_length s - 2) retourne la chaˆıne s priv´ee de son premier et de son dernier caract`ere. Ceci se traduit par la fonction r´ecursive suivante : # let rec palindrome s = let longueur = string_length s in if longueur <= 1 then true else if s.[0] = s.[longueur - 1] then palindrome (sub_string s 1 (longueur - 2)) else false;; palindrome : string -> bool = # palindrome "serres";; - : bool = true # palindrome "toto";; - : bool = false Op´erateurs bool´eens Cette fonction s’´ecrit plus ´el´egamment `a l’aide des op´erateurs « et » et « ou » des bool´eens. Par d´efinition, si P et Q sont des bool´eens, alors l’expression P et Q est vraie quand P et Q sont vraies simultan´ement. Naturellement, l’expression P ou Q est vraie d`es que P ou bien Q est vraie et a fortiori si P et Q sont vraies. En Caml le « ou » se note || et le « et » &&. Les op´erateurs || et && remplacent certaines formes d’expressions conditionnelles. En effet, la construction if P then true else Q calcule la mˆeme valeur bool´eenne que P || Q et de mˆeme if P then Q else false calcule la mˆeme valeur que P && Q. Bien sˆur, if P then true else false se simplifie en P. On clarifie souvent les programmes en appliquant ces ´equivalences. Voici donc une version plus simple de palindrome qui utilise les op´erateurs bool´eens : # let rec palindrome s = let longueur = string_length s in (longueur <= 1) || (s.[0] = s.[longueur - 1]) && (palindrome (sub_string s 1 (longueur - 2)));; palindrome : string -> bool = Les constructions || et && ont les mˆemes priorit´es relatives que + et *, c’est-`a-dire le mˆeme parenth´esage implicite. Ainsi, de la mˆeme fa¸con que a + b * c se lit en fait a + (b * c), l’expression a || b && c est lue a || (b && c) par la machine. On lit alors le code de palindrome tr`es naturellement : une chaˆıne est un palindrome si sa longueur est inf´erieure `a 1, ou si ses caract`eres de d´ebut et de fin sont les mˆemes et que la sous-chaˆıne qu’ils d´elimitent est un palindrome. D´efinitions par cas : le filtrage 27 Fonction r´ecursive `a plusieurs arguments Pour plus d’efficacit´e, nous r´e´ecrivons la fonction palindrome en comparant directement les caract`eres de la chaˆıne argument deux `a deux, sans cr´eer de sous-chaˆınes. On prend donc deux indices dans la chaˆıne argument s. L’indice i d´emarre au premier caract`ere ; l’indice j d´emarre au dernier caract`ere (au d´epart de la boucle on a donc n´ecessairement i ≤ j, sauf si la chaˆıne est vide). `A chaque ´etape, on compare les caract` eres d’indice i et j. S’ils sont ´egaux, on continue ; sinon, la chaˆıne n’est ´evidemment pas un palindrome. La r´ecursion s’arrˆete quand l’indice i atteint ou d´epasse j. Dans le cas i = j, on est sur le caract`ere central de la chaˆıne et il n’y a rien `a faire (il ne reste qu’un caract`ere `a examiner forc´ement ´egal `a lui-mˆeme) : s est un palindrome. Dans le cas i > j, il n’y a rien `a faire non plus : on a d´epass´e le milieu de la chaˆıne en ayant compar´e deux `a deux tous les caract`eres, donc s est un palindrome. Cet exemple nous fournit notre premi`ere fonction r´ecursive `a plusieurs arguments. # let rec palin s i j = (i >= j) || (s.[i] = s.[j]) && (palin s (i + 1) (j - 1));; palin : string -> int -> int -> bool = # let palindrome s = palin s 0 (string_length s - 1);; palindrome : string -> bool = # palindrome "eluparcettecrapule";; - : bool = true On simplifie encore un peu ce programme en ´ecrivant la palin `a l’int´erieur de palindrome, ce qui lui ˆote l’argument s, qui est li´e par la fonction palindrome et qui est donc visible par la fonction locale palin. C’est la version la plus jolie. Par coquetterie, nous avons aussi supprim´e les parenth`eses autour des tests, car elles sont implicites. # let palindrome s = let rec palin i j = i >= j || s.[i] = s.[j] && palin (i + 1) (j - 1) in palin 0 (string_length s - 1);; palindrome : string -> bool = # palindrome "tulaStroP´ecras´eC´esarc´ePortSalut";; - : bool = true 2.2 D´efinitions par cas : le filtrage Nous avons donn´e la d´efinition r´ecursive suivante de la fonction factorielle : let rec factorielle n = if n = 0 then 1 else n * factorielle (n - 1);; Caml dispose d’une mani`ere encore plus concise de d´efinir cette fonction : l’analyse de cas. Il y a ici deux cas possibles pour l’argument de factorielle, ou bien c’est 0 ou bien il est diff´erent de 0. On l’´ecrit ainsi : # let rec factorielle = function | 0 -> 1 | n -> n * factorielle (n - 1);; factorielle : int -> int = 28 R´ecursivit´e L’analyse de cas | 0 -> 1 | n -> n * factorielle (n - 1) signifie simplement : si l’argument de la fonction est 0 alors renvoyer 1, sinon nommer n l’argument de la fonction et retourner n * factorielle (n - 1). La barre verticale « | » introduit donc les cas et correspond `a un « ou bien », tandis que la fl`eche « -> » indique ce qu’il faut calculer dans le cas correspondant. L’analyse de cas porte le nom technique de filtrage que nous emploierons d´esormais. Le filtrage est un trait extrˆemement puissant de Caml. Il est int´egr´e dans de nombreuses constructions du langage et tr`es fr´equemment employ´e dans les programmes. Il arrive dans certains cas qu’on n’utilise pas l’argument de la fonction pour calculer le r´esultat : # let ´egal_un = function | 1 -> true | x -> false;; ´egal_un : int -> bool = Pour bien montrer que le nom x ne sert `a rien, puisque sa valeur n’est pas n´ecessaire pour retourner false, on se sert d’un symbole sp´ecial « _ » (le soulign´e), qui signifie « dans tous les autres cas » : # let est_un = function | 1 -> true | _ -> false;; est_un : int -> bool = # est_un 1;; - : bool = true # est_un 0;; - : bool = false Nous abordons maintenant un probl`eme apparemment tr`es difficile, qu’une fonction r´ecursive r´esout sans difficult´e et avec une grande ´el´egance. 2.3 Les tours de Hanoi La l´egende Le jeu des tours de Hanoi consiste en une plaquette de bois sur laquelle sont plant´ees trois tiges. Sur ces tiges sont enfil´es des disques de bois dont les diam`etres sont tous diff´erents. La seule r`egle du jeu est de ne jamais poser un disque sur un disque plus petit que lui, et de ne d´eplacer qu’un seul disque `a la fois. Au d´ebut du jeu tous les disques sont pos´es sur la tige de gauche. Le but du jeu est de d´eplacer les disques d’une tige sur l’autre, sans jamais violer la r`egle, pour finalement les amener tous sur la tige de droite. Le jeu original ´etait accompagn´e d’une notice racontant la l´egende de moines d’un temple de Hanoi qui passaient leur temps `a r´esoudre ce jeu pour atteindre le nirvana. En effet, les moines croyaient que la fin du monde arriverait lorsque le jeu serait achev´e. Leur jeu grandeur nature occupait la cour d’un temple. Il se composait de 64 disques d’or et de trois tiges d’ivoire d’un m`etre de haut. Cette l´egende a ´et´e invent´ee par le math´ematicien fran¸cais Edouard Lucas en 1883. Notre but est d’´ecrire un programme qui indique les mouvements `a faire pour r´esoudre le jeu. Si vous n’ˆetes pas trop impatient, cherchez quelques instants vousm ˆeme la solution. C’est difficile, n’est-ce pas ? Et pourtant, ce jeu est ´etrangement facile `a r´esoudre avec une proc´edure r´ecursive. Les tours de Hanoi 29 A plus gros disque autres disques B C D´eplacer les autres disques sur B en respectant la r`egle : hanoi A C B (n - 1) A B C plus gros disque autres disques D´eplacer le disque restant vers C : mouvement A C A B C plus gros disque autres disques D´eplacer les autres disques de B vers C en respectant la r`egle : hanoi B A C (n - 1) A B C plus gros disque autres disques Figure 2.1: Comment r´esoudre le probl`eme des tours de Hanoi. 30 R´ecursivit´e Le programme Supposons que les tiges s’appellent A, B et C, que n soit le nombre de disques, tous pos´es au d´epart sur la tige A, et que nous devions les mettre sur la tige C. L’astuce consiste `a se rendre compte que si nous savions comment r´esoudre le probl`eme pour n − 1 disques alors nous saurions le faire pour n, sans violer la r`egle. En effet, si l’on suppose les n − 1 disques d´ej`a pos´es sur la tige B, le dernier disque encore pos´e sur la tige A est le plus gros disque. Il nous suffit donc de le poser sur la tige C qui est vide (pas de violation possible de la r`egle), puis de d´eplacer les n − 1 disques de la tige B `a la tige C. C’est possible puisque nous supposons savoir comment d´eplacer n−1 disques d’une tige `a une autre et puisque c’est le plus gros disque qui est maintenant pos´e sur C, il n’y a pas de violation de la r`egle en posant les n−1 disques de B sur la tige C (voir la figure 2.1). Mais nous savons aussi r´esoudre le probl`eme pour 0 disques : il n’y a rien `a faire. Nous savons donc r´esoudre le probl`eme des tours de Hanoi pour tout n. C’est encore plus facile `a dire en Caml : nous d´efinissons d’abord une fonction auxiliaire pour imprimer les mouvements, puis la proc´edure principale. # let mouvement de vers = print_string ("D´eplace un disque de la tige " ^ de ^ " `a la tige " ^ vers); print_newline ();; mouvement : string -> string -> unit = # let rec hanoi d´epart milieu arriv´ee = function | 0 -> () | n -> hanoi d´epart arriv´ee milieu (n - 1); mouvement d´epart arriv´ee; hanoi milieu d´epart arriv´ee (n - 1);; hanoi : string -> string -> string -> int -> unit = Les noms des arguments d´epart, milieu et arriv´ee sont ´echang´es lorsque nous voulons d´eplacer les disques d’une tige `a l’autre : par exemple, pour d´eplacer un disque de la tige de nom d´epart vers la tige argument milieu, nous ´ecrivons hanoi d´epart arriv´ee milieu 1. # hanoi "A" "B" "C" 3;; D´eplace un disque de la tige A `a la tige C D´eplace un disque de la tige A `a la tige B D´eplace un disque de la tige C `a la tige B D´eplace un disque de la tige A `a la tige C D´eplace un disque de la tige B `a la tige A D´eplace un disque de la tige B `a la tige C D´eplace un disque de la tige A `a la tige C - : unit = () V´erifiez : le casse-tˆete est r´esolu. C’est magique ! On n’a pas vraiment l’impression d’avoir ´ecrit un programme si savant . . . Ne vous inqui´etez pas si vous ´eprouvez des difficult´es `a comprendre la proc´edure hanoi. C’est normal, car c’est le premier exemple de programme Caml qui nous oblige `a changer franchement notre fa¸con d’appr´ehender les programmes. En effet, il est extrˆemement difficile de comprendre comment la proc´edure marche. Au contraire, il Notions de complexit´e 31 faut se demander pourquoi elle marche. Le pourquoi est simple : il est enti`erement contenu dans la figure 2.1. Si vous ˆetes persuad´e du bien-fond´e de la m´ethode de r´esolution que la figure sugg`ere et que vous ˆetes convaincu que la proc´edure hanoi impl´emente correctement cette m´ethode, alors ne cherchez pas plus loin : vous avez tout compris. Si en revanche vous essayez de suivre le d´eroulement des appels r´ecursifs et les permutations d’arguments qui se d´eroulent `a l’ex´ecution de la proc´edure (par exemple en utilisant la trace de Caml), vous serez vite perdu. En fait, mˆeme si vous suiviez pr´ecautionneusement ce d´eroulement vous n’en apprendriez pas plus, si ce n’est que ¸ca marche, puisque vous constateriez que les bons arguments se mettent en place au bon moment pour produire les bons r´esultats, comme par miracle. Il faut se d´ecider `a penser que ce suivi pas `a pas du d´eroulement des programmes est du ressort de la machine exclusivement. Notre compr´ehension est de bien plus haut niveau : elle consiste essentiellement `a prouver que le programme ne peut que marcher ; comment le programme parvient effectivement au bon r´esultat ne nous regarde pas. Il est heureux que cette noble activit´e de r´eflexion sur le bien-fond´e d’une m´ethode de r´esolution d’un probl`eme nous appartienne en propre, alors que nous d´el´eguons aux machines la mise en oeuvre effective. Un ´equilibre se cr´ee ainsi : si la r´eflexion sur la m´ethode est hors de port´ee de la machine, la gestion sans erreurs des passages de param`etres et la reprise des appels r´ecursifs en suspens est un jeu d’enfant pour la machine, alors que nous serions incapables d’une telle rigueur. Rendons donc aux machines ce qui appartient aux machines. Pour ceux que cela int´eresse, la section suivante esquisse les fondements th´eoriques de la m´ethode qui explique pourquoi la proc´edure hanoi marche effectivement. Accessoirement, il permet aussi de calculer la date `a laquelle les moines auront achev´e leur travail et donne donc une bonne id´ee de la date de la fin du monde ! Si tout cela ne vous pr´eoccupe pas, passez directement au chapitre suivant. 2.4 Notions de complexit´e La complexit´e est l’´etude du nombre d’op´erations n´ecessaires `a l’ach`evement d’un calcul. Une analyse de complexit´e permet donc de se faire une id´ee du temps de calcul n´ecessaire `a l’ach`evement d’un programme, en fonction de l’argument qui lui est soumis. En g´en´eral, on compte le nombre d’op´erations ´el´ementaires (additions, multiplications, soustractions et divisions, comparaisons de valeurs, affectations d’´el´ements de tableau) et/ou le nombre d’appels de fonctions. Par exemple, la fonction successeur demande une seule op´eration, quel que soit son argument. En revanche, la complexit´e de la fonction factorielle d´epend de son argument : elle demande n op´erations pour l’argument n. Plus pr´ecis´ement, il faut n multiplications, n+1 appels r´ecursifs `a la fonction factorielle et n soustractions. Si l’on consid`ere que ces trois types d’op´erations ont des coˆuts voisins, alors la complexit´e de factorielle est de l’ordre de 2n+(n+1), c’est-`a-dire de l’ordre de 3n. On consid´erera donc que la fonction factorielle a une complexit´e qui augmente au mˆeme rythme que son argument, ce qu’on note O(n) et qu’on prononce « grand-o de n ». Plus pr´ecis´ement, O(n) signifie « un certain nombre de fois » n, plus des termes n´egligeables devant n quand n devient grand, comme par exemple une constante. On ne s’int´eresse en effet qu’`a un ordre de grandeur de la com32 R´ecursivit´e plexit´e : cette complexit´e augmente-t-elle comme l’argument (algorithme lin´eaire), ou comme le carr´e de l’argument (algorithme quadratique), ou comme une exponentielle de l’argument (algorithme exponentiel ) ? Dans le cas de factorielle, on r´esume l’´etude en notant une complexit´e lin´eaire O(n), puisque la complexit´e r´eelle est 3n + 1. Principe de r´ecurrence Les ´etudes de complexit´e et les d´efinitions r´ecursives de fonctions reposent sur un raisonnement simple sur les propri´et´es qui concernent les nombres entiers : le principe de r´ecurrence. Nous allons l’expliquer, puis l’utiliser pour d´emontrer des propri´et´es de la fonction hanoi. Le principe de r´ecurrence s’´enonce informellement ainsi : si une certaine propri´et´e sur les nombres entiers est vraie pour 0 et si la propri´et´e est vraie pour le successeur d’un nombre d`es qu’elle est vraie pour ce nombre, alors cette propri´et´e est vraie pour tous les nombres. Formellement : soit P(n) une propri´et´e qui d´epend d’un entier n. Si les phrases suivantes sont vraies : 1. P(0) est vraie, 2. si P(n) est vraie alors P(n + 1) est vraie, alors P(n) est vraie pour tout n. Ce principe est en fait ´evident : les deux propri´et´es demand´ees par le principe de r´ecurrence permettent facilement de d´emontrer la propri´et´e P pour toute valeur enti`ere. Par exemple, supposons que P v´erifie les deux propri´et´es et qu’on veuille d´emontrer que P est vraie pour 2. Puisque P est vraie pour 0 elle est vraie pour son successeur, 1. Mais puisque P est vraie pour 1 elle est vraie pour son successeur, donc elle est vraie pour 2. Il est clair que ce raisonnement se poursuit sans probl`eme pour tout nombre entier fix´e `a l’avance. C’est ce principe que nous avons utilis´e pour r´esoudre le probl`eme des tours de Hanoi : 1. nous avons montr´e que nous savions le r´esoudre pour 0 disque ; 2. nous avons montr´e qu’en sachant le r´esoudre pour n − 1 disques nous savions le r´esoudre pour n disques. Ces deux cas correspondent exactement aux deux clauses de la fonction hanoi (cas 0 -> et cas n ->). Le principe de r´ecurrence nous prouve donc que nous savons effectivement r´esoudre le probl`eme pour tout n, mˆeme si cela ne nous apparaissait pas clairement au d´epart. La difficult´e intuitive de ce genre de d´efinitions r´ecursives est d’oser utiliser l’hypoth`ese de r´ecurrence : il faut supposer qu’on sait d´ej`a faire pour n − 1 disques et ´ecrire le programme qui r´esout le probl`eme pour n disques. Dans la proc´edure hanoi, on suppose ainsi deux fois que la fonction saura bien faire toute seule pour n − 1 disques et l’on ne s’occupe que de d´eplacer le gros disque, ce qui semble un travail facile. Finalement, on a l’impression de voir tourner du code que l’on n’a pas ´ecrit, tellement il semble astucieux `a l’ex´ecution. L’´ecriture de fonctions r´ecursives se r´eduit ainsi tr`es souvent au sch´ema : Notions de complexit´e 33 let rec f = function | 0 -> « solution simple » | n -> ... f (n - 1) ... f (n - 1) ...;; On d´emontre en math´ematiques qu’il n’est pas interdit d’appeler f sur d’autres arguments que n - 1, pourvu qu’ils soient plus petits que n (par exemple n - 2), mais alors il faut pr´evoir d’autres cas simples (par exemple 1 ->). Un exemple de ce sch´ema de programme est la fonction de Fibonacci d´efinie par : # let rec fib = function | 0 -> 1 | 1 -> 1 | n -> fib (n - 1) + fib (n - 2);; fib : int -> int = # fib 10;; - : int = 89 Remarquez que cette fonction fait effectivement deux appels r´ecursifs sur deux valeurs diff´erentes, mais toutes les deux plus petites que l’argument donn´e. Complexit´e de la proc´edure hanoi Il est facile d’´ecrire un programme qui compte le nombre de mouvements n´ecessaires pour r´esoudre le jeu pour n disques : il y a 0 mouvement `a faire pour 0 disque, l’appel `a la proc´edure mouvement produit 1 mouvement et le nombre de mouvements n´ecessaires aux appels r´ecursifs est forc´ement compt´e par la fonction r´ecursive de comptage que nous sommes en train de d´efinir. En effet, on suppose une fois de plus que pour n − 1 la fonction « sait faire » et on se contente de trouver le r´esultat pour n. # let rec compte_hanoi d´epart milieu arriv´ee = function | 0 -> 0 | n -> compte_hanoi d´epart arriv´ee milieu (n - 1) + 1 + compte_hanoi milieu d´epart arriv´ee (n - 1);; compte_hanoi : ’a -> ’a -> ’a -> int -> int = Les arguments contenant les noms des tiges sont bien sˆur inutiles et il suffit d’´ecrire : # let rec compte_hanoi_na¨ıf = function | 0 -> 0 | n -> compte_hanoi_na¨ıf (n - 1) + 1 + compte_hanoi_na¨ıf (n - 1);; compte_hanoi_na¨ıf : int -> int = qu’on simplifie encore en # let rec compte_hanoi = function | 0 -> 0 | n -> (2 * compte_hanoi (n - 1)) + 1;; compte_hanoi : int -> int = # compte_hanoi 3;; - : int = 7 # compte_hanoi 10;; - : int = 1023 # compte_hanoi 16;; - : int = 65535 On devine la propri´et´e suivante : pour tout n, compte_hanoi (n) = 2n −1. Nous allons la d´emontrer en utilisant le principe de r´ecurrence. Nous d´efinissons donc formellement 34 R´ecursivit´e la propri´et´e P par : P(n) est vraie si et seulement si compte_hanoi (n) = 2n − 1. La proposition P(0) est vraie car compte_hanoi (0) = 0 et 20 − 1 = 1 − 1 = 0. Supposons P(n) vraie et montrons qu’alors P(n + 1) est vraie. Pour montrer P(n + 1), il faut d´emontrer compte_hanoi (n + 1) = 2n+1 − 1. Or, d’apr`es la d´efinition de la fonction compte_hanoi, on a : compte_hanoi (n + 1) = 2 × compte_hanoi ((n + 1) − 1) + 1, soit compte_hanoi (n + 1) = 2 × compte_hanoi (n) + 1. Mais, par hypoth`ese de r´ecurrence, P(n) est vraie, donc compte_hanoi (n) = 2n−1. En reportant dans l’´egalit´e pr´ec´edente, on obtient : compte_hanoi (n + 1) = 2 × (2n − 1) + 1. Mais 2 × (2n − 1) + 1 = 2n+1 − 2 + 1 = 2n+1 − 1, donc compte_hanoi (n + 1) = 2n+1 − 1 et P(n + 1) est vraie. Il s’ensuit, d’apr`es le principe de r´ecurrence, que P(n) est vraie pour tout n. Avec ce nouveau r´esultat, nous sommes autoris´es `a red´efinir compte_hanoi comme la fonction qui `a n associe 2n − 1. Pour avoir une id´ee du nombre de mouvements n´ecessaires pour r´esoudre le probl`eme avec 64 disques, nous sommes oblig´es de faire les calculs en « virgule flottante » car le r´esultat exc`ede de beaucoup la limite sup´erieure des entiers repr´esentables en Caml. Nous reviendrons plus tard sur les nombres en virgule flottante, aussi appel´es nombres flottants (chapitre 8). Pour l’instant il suffit de savoir qu’un nombre flottant est caract´eris´e par le point qui pr´ec`ede sa partie d´ecimale et que les op´erations associ´ees aux flottants sont suffix´ees ´egalement par un point (+., -., *., etc.). Nous impl´ementons donc notre fonction en utilisant la fonction « puissance » des nombres flottants (power). # let compte_hanoi_rapide n = power 2.0 n -. 1.0;; compte_hanoi_rapide : float -> float = # compte_hanoi_rapide 64.0;; - : float = 1.84467440737e+19 Un algorithme correct mais inutilisable Grˆace `a notre d´emonstration math´ematique, nous avons ´etabli une formule de calcul direct du nombre de mouvements n´ecessaires `a la r´esolution du jeu pour n disques. Nous avons ainsi tr`es fortement acc´el´er´e la fonction compte_hanoi. C’´etait indispensable car notre premi`ere version, la fonction compte_hanoi_na¨ıf, quoique parfaitement correcte d’un point de vue math´ematique, n’aurait pas pu nous fournir le r´esultat pour 64. En effet cette version calcule son r´esultat en utilisant uniquement l’addition. Plus pr´ecis´ement, elle n’ajoute toujours que des 1 : il lui aurait donc fallu faire 264 − 1 additions. Mˆeme en supposant qu’on fasse 1 milliard d’additions par seconde, ce qui est `a la limite de la technologie actuelle, il aurait fallu, avec le programme de la premi`ere version de compte_hanoi, Notions de complexit´e 35 # let nombre_de_secondes_par_an = 3600.0 *. 24.0 *. 365.25;; nombre_de_secondes_par_an : float = 31557600.0 # let nombre_d’additions_par_an = nombre_de_secondes_par_an *. 1E9;; nombre_d’additions_par_an : float = 3.15576e+16 # compte_hanoi_rapide 64.0 /. nombre_d’additions_par_an;; - : float = 584.542046091 c’est-`a-dire plus de 584 ann´ees pour achever le calcul ! Nous sommes donc ici en pr´esence d’une fonction qui donne effectivement le bon r´esultat au sens des math´ematiques, mais qui le calcule tellement lentement qu’elle devient inutilisable. `A la diff´erence des math´ematiques, il ne suffit donc pas en informatique d’´ecrire des programmes corrects, il faut encore que leur complexit´e ne soit pas trop ´elev´ee pour qu’ils calculent le r´esultat correct en un temps raisonnable. La fonction compte_hanoi_na¨ıve n´ecessite 2n−1 additions pour l’argument n. Son temps de calcul est donc proportionnel `a une puissance (2n) dont l’exposant est son argument n : l’algorithme est exponentiel. La seconde version utilisant la multiplication n´ecessite n multiplications, l’algorithme est donc lin´eaire. Un algorithme lin´eaire demande un temps de calcul qui augmente comme la valeur de son argument (O(n)), ce qui est raisonnable. En effet, cette version nous aurait permis d’obtenir notre r´esultat, puisque pour n = 64 il aurait fallu 64 multiplications seulement. La derni`ere version, quant `a elle, est en temps constant. Elle ne n´ecessite que deux op´erations flottantes quel que soit son argument : c’est l’algorithme id´eal. On retiendra qu’un algorithme exponentiel est vite susceptible d’exiger un temps de calcul prohibitif quand son argument augmente. Date de la fin du monde Calculons le nombre d’ann´ees n´ecessaires aux moines pour achever leur jeu `a 64 disques. Supposons qu’ils puissent effectuer sans arrˆet, jour et nuit, dix mouvements par secondes, ce qui est vraiment le maximum qu’on puisse exiger de ces pauvres moines. Il leur faudrait alors : # let nombre_de_mouvements_par_an = nombre_de_secondes_par_an *. 10.0;; nombre_de_mouvements_par_an : float = 315576000.0 # compte_hanoi_rapide 64.0 /. nombre_de_mouvements_par_an;; - : float = 58454204609.1 soit plus de 58 milliards d’ann´ees. C’est beaucoup plus que la dur´ee de vie estim´ee du Soleil. Il semble donc que l’heure de la fin du monde aura sonn´e tr`es longtemps avant la fin du jeu ! Calcul de la complexit´e de la seconde version Dans la section pr´ec´edente, nous avons affirm´e que la seconde version de compte_hanoi : # let rec compte_hanoi = function | 0 -> 0 | n -> 2 * compte_hanoi (n - 1) + 1;; compte_hanoi : int -> int = 36 R´ecursivit´e n´ecessitait n multiplications. La d´emonstration en est tr`es simple. Nous noterons Op(compte_hanoi (n)) le nombre d’op´erations n´ecessaires pour effectuer le calcul de compte_hanoi (n) `a l’aide de cette version de compte_hanoi. Nous d´emontrons par r´ecurrence la propri´et´e P(n) d´efinie par : P(n) est vraie si et seulement si Op(compte_hanoi (n)) = n. La propri´et´e P(0) est vraie car Op(compte_hanoi (0)) = 0. Supposons P(n) vraie et montrons qu’alors P(n+1) est vraie. Pour montrer P(n+1), il faut d´emontrer Op(compte_hanoi (n + 1)) = (n + 1). Or, d’apr`es le code de la fonction compte_hanoi, quand on a le r´esultat de compte_hanoi (n - 1), il faut faire une multiplication de plus pour obtenir compte_hanoi (n). On a donc : Op(compte_hanoi (n+1)) = 1+Op(compte_hanoi (n)) ; mais, d’apr`es l’hypoth`ese de r´ecurrence, Op(compte_hanoi (n)) = n, et donc Op(compte_hanoi (n+1)) = n+1. Il s’ensuit que P(n) est vraie pour tout n. Remarquons pour finir que nous avons calcul´e la complexit´e de hanoi en utilisant la fonction compte_hanoi, dont nous avons dˆu `a nouveau ´etudier la complexit´e, pour l’optimiser (sous peine de ne pas obtenir effectivement la complexit´e de hanoi). Il faut d´ecid´ement r´efl´echir sur les programmes qu’on ´ecrit . . . 3 Programmation imp´erative O`u l’on apprend que 2x + 2x font 4x. ous mettons en place dans ce chapitre quelques outils indispensables `a la programmation imp´erative. En particulier, nous introduisons la notion de tableau, et l’utilisons pour calculer des identit´es remarquables. Nous serons par exemple en mesure d’´etablir par programme la formule (x + 1)2 = x2 + 2x + 1. En termes savants nous ferons du calcul formel sur des polynˆomes `a une ind´etermin´ee. Si vous savez d´ej`a qu’il y a autre chose dans la vie que la programmation fonctionnelle et que vous connaissez les boucles « for » et «while », vous pouvez sauter ce chapitre. 3.1 La programmation imp´erative Jusqu’`a pr´esent, nous avons ´ecrit de petits programmes dans un sous-ensemble de Caml : la partie d´eclarative, la plus proche des math´ematiques. Nous avons toujours d´efini des fonctions qui retournaient le r´esultat que nous voulions calculer. Ces fonctions calculent le r´esultat souhait´e au sens des calculs math´ematiques, c’est-`a-dire par simplifications successives d’une expression. Ce style de programmation `a l’aide de fonctions s’appelle la programmation fonctionnelle. Une autre fa¸con de calculer consiste `a consid´erer qu’un calcul est un processus ´evolutif, o`u le temps a son importance. Il s’agit de modifier un ´etat : l’ordinateur commence l’ex´ecution du programme dans un certain ´etat initial, que l’ex´ecution du programme modifie jusqu’`a parvenir `a un ´etat final qui contient le r´esultat voulu. On change l’´etat courant par modification du contenu de la m´emoire de l’ordinateur (`a l’aide d’affectations), ou encore par interaction avec le monde ext´erieur : interrogation de l’utilisateur, affichage de r´esultats, lecture ou ´ecriture de fichiers, bref tout ce qu’on nomme les entr´ees-sorties. Toutes ces op´erations qui modifient physiquement le contenu des adresses m´emoire sont appel´ees effets (ou encore effets de bord) : Un effet est une modification d’une case de la m´emoire (tableau ou r´ef´erence), ou encore une interaction avec le monde ext´erieur (impression ou lecture). 38 Programmation imp´erative Ce style de programmation par effets s’appelle la programmation imp´erative. Ce nom provient ´evidemment de la signification du mode imp´eratif dans la conjugaison des verbes. En effet, les programmes imp´eratifs d´ecrivent explicitement `a la machine la suite des op´erations `a effectuer (fais ci, fais ¸ca). Au contraire, en programmation fonctionnelle, on laisse la machine calculer le r´esultat comme elle le peut `a partir d’une formule, sans lui pr´eciser compl`etement l’ordre dans lequel elle doit op´erer. Par exemple, pour calculer le carr´e d’un nombre x, on ´ecrit x * x en programmation fonctionnelle. Au contraire, une m´ethode imp´erative serait de r´eserver une case m´emoire comme accumulateur, de l’initialiser avec x, puis de remplacer le contenu de cet accumulateur par son contenu multipli´e par lui-mˆeme. Le r´esultat cherch´e serait maintenant dans l’accumulateur. Dans un cas si simple, ces descriptions sont ´evidemment caricaturales, mais l’id´ee est la bonne. Le style imp´eratif implique la modification de l’´etat de la m´emoire, donc l’utilisation de structures de donn´ees modifiables (par exemple les tableaux dont les ´el´ements peuvent ˆetre chang´es dynamiquement) et l’emploi de commandes. Les commandes sont des expressions qui ne retournent pas de valeurs int´eressantes ; leur r´esultat est simplement une modification de l’´etat courant, c’est-`a-dire un effet. Lorsqu’une fonction se contente d’ex´ecuter une s´erie de commandes, on l’appelle souvent proc´edure. Une proc´edure en Caml est donc simplement une fonction qui se contente de faire des effets, sans produire de r´esultat au sens math´ematique. Nous aurions pu nous cantonner au sous-ensemble fonctionnel de Caml et cependant ´ecrire de tr`es jolis programmes. Mais c’eˆut ´et´e donner une fausse image de Caml : ce qui fait la puissance du langage c’est justement qu’il ne se limite pas `a la programmation fonctionnelle, mais int`egre harmonieusement programmation fonctionnelle et programmation imp´erative. De plus, nous cherchons avant tout `a vous montrer les programmes les plus simples et les plus clairs possibles : nous avons donc besoin de tous les outils que Caml met `a notre disposition. De surcroˆıt, la programmation imp´erative n’est pas seulement indispensable pour traiter les probl`emes d’interaction avec le monde ext´erieur (entr´ees-sorties). Dans certains cas un algorithme, c’est-`a-dire une m´ethode de r´esolution d’un probl`eme, exige moins de calculs lorsqu’il est ´ecrit en style imp´eratif que lorsqu’il est ´ecrit en style fonctionnel. Enfin, certains algorithmes s’expriment naturellement en termes d’´evolution d’un ´etat ; la programmation imp´erative s’impose alors. Nous avons pour l’instant illustr´e les effets d’entr´ees-sorties, plus pr´ecis´ement les impressions. Nous allons maintenant faire des effets sur la m´emoire, ce qu’on appelle aussi des modifications physiques ou modifications en place de donn´ees. Pour cela il nous faut disposer de cases m´emoire modifiables par le programme. Caml propose pour cela les notions de r´ef´erences et de tableaux. Nous commen¸cons par ´etudier les tableaux, qui sont plus simples. Puisque la notion de temps intervient en programmation imp´erative, il nous faut un moyen de sp´ecifier au langage « fait ceci d’abord » et « fait cela ensuite » : c’est la notion de s´equence que nous avons d´ej`a vue au chapitre 1. Nous avons ´egalement besoin de r´ep´eter des suites d’effets : c’est la notion de boucles. Nous d´ecrivons ces construction, puis appliquons ces outils au calcul sur les polynˆomes. Boucles 39 3.2 Boucles Caml fournit deux sortes de boucles pour r´ep´eter des effets : la boucle « pour » et la boucle « tant que ». La boucle « pour » r´ep`ete un calcul un nombre de fois fix´e `a l’avance ; la boucle « tant que » r´ep`ete un calcul tant qu’une condition reste vraie. Boucle « tant que » Boucle « tant que » ::= while expression (while : tant que) do expression done (do : faire, done : fait) La signification de while condition do actions done est simplement de faire les actions tant que la condition est vraie. La condition est test´ee au d´ebut de chaque it´eration. Si elle est initialement fausse, les actions ne sont jamais ex´ecut´ees. Dans certains cas, la boucle « tant que » sert `a r´ep´eter ind´efiniment les mˆemes actions jusqu’`a un ´ev´enement exceptionnel. Dans ce cas, la condition de boucle est tout simplement le bool´een true, comme dans while true do actions done. Boucle « pour » Boucle « pour » ::= for ident = expression (for : pour) (to | downto) expression (to : jusqu’`a, down : en bas) do expression done (do : faire, done : fait) La s´emantique, c’est-`a-dire la signification, de l’expression for i = d´ebut to fin do actions done est de faire les actions avec i = d´ebut, puis avec i = d´ebut + 1 et ainsi de suite, jusqu’`a i = fin. En particulier, si d´ebut > fin, on n’´evalue jamais actions. Pour la version downto, on d´ecr´emente l’indice de boucle i (on lui soustrait 1) `a chaque tour, au lieu de l’incr´ementer (lui ajouter 1). L’indice de boucle est forc´ement du type entier. Le nom associ´e `a l’indice de boucle est introduit par la boucle (comme par une liaison let) ; sa liaison n’est valide que pendant le corps de la boucle. Prenons un exemple simple : nous imprimons les dix chiffres `a l’aide d’une boucle de 0 `a 9. Nous d´efinissons une proc´edure imprime_chiffre dont l’argument est « rien », et nous la d´eclenchons en l’appliquant `a « rien ». # let imprime_chiffres () = for i = 0 to 9 do print_int i done; print_newline ();; imprime_chiffres : unit -> unit = # imprime_chiffres ();; 0123456789 - : unit = () 40 Programmation imp´erative 3.3 Manipulation de polynˆomes Nous continuons l’apprentissage de la programmation imp´erative par l’´etude des tableaux. `A titre d’illustration, nous ´ecrivons un jeu de fonctions qui impl´ementent les op´erations de base sur les polynˆomes. Avant de nous lancer dans la programmation, nous rappelons bri`evement ce que sont les polynˆomes. Les polynˆomes `a une ind´etermin´ee Des classes ´el´ementaires, on retient souvent qu’un polynˆome est une somme de puissances de x. Par exemple, p = x2+2x+3 est un polynˆome. La variable x est appel´ee l’ind´etermin´ee du polynˆome. Un polynˆome est une somme de termes ´el´ementaires qu’on nomme monˆomes (par exemple x2 et 2x). ´Etant donn´ee une variable x, on appelle monˆome de coefficient ai et de degr´e i l’expression ai xi. Le degr´e d’un polynˆome est celui de son monˆome de plus haut degr´e. On rappelle que x1 = x et x0 = 1. Le monˆome de degr´e 0 est donc r´eduit `a une constante (c’est 3 pour p) et celui de degr´e 1 au produit d’un nombre par l’ind´etermin´ee (c’est 2x pour p). D’autre part, nous utiliserons la propri´et´e : pour tout n et m entiers positifs, xn × xm = xn+m. Nous mod´elisons les polynˆomes `a l’aide de tableaux d’entiers : le tableau des coefficients de leurs monˆomes. Les degr´es seront donc implicites, simplement d´etermin´es par l’indice du coefficient dans le tableau qui repr´esente le polynˆome. Par exemple, le polynˆome p = x2 + 2x + 3 sera repr´esent´e par le tableau contenant les nombres 3, 2, 1 dans cet ordre, puisque 3 est le coefficient de degr´e 0 de p, 2 est le coefficient de degr´e 1 et 1 le coefficient de degr´e 2. Nous ´etudions donc maintenant bri`evement les tableaux de Caml. Tableaux Les tableaux, aussi appel´es « vecteurs », sont des suites finies et modifiables de valeurs d’un mˆeme type. Leur type est not´e ′a vect (o`u ′a signifie « n’importe quel type »). Puisque les ´el´ements des tableaux sont tous de la mˆeme nature (du mˆeme type), on qualifie les tableaux de suites homog`enes de valeurs. Les valeurs d’un tableau sont enregistr´ees dans des cellules de m´emoire cons´ecutives. Les positions des ´el´ements dans un tableau d´ebutent `a la position 0. Construction de tableaux Un tableau se d´efinit de deux fa¸cons : soit en dressant directement la liste de ses ´el´ements, soit en cr´eant le tableau et en remplissant ses cases ult´erieurement. Si un tableau est d´efini par la liste de ses ´el´ements, cette liste est entour´ees des symboles [| et |], tandis que les ´el´ements sont s´epar´es par des « ; ». Notre polynˆome p = x2+2x+3 se d´efinit donc par la phrase : # let p = [| 3; 2; 1 |];; p : int vect = [|3; 2; 1|] Graphiquement, on repr´esente naturellement les tableaux par une succession de cases. Par exemple, p sera repr´esent´e ainsi : Manipulation de polynˆomes 41 p 3 2 1 |0 {1z 2} indices Pour construire des tableaux dont on remplira les cases plus tard, on dispose de la fonction pr´ed´efinie make_vect. Avec cette fonction, on cr´ee un tableau en donnant sa taille et un ´el´ement qui sera mis au d´epart dans toutes les cases du tableau : la valeur d’initialisation du tableau. D´efinissons par exemple un tableau de taille 4 contenant des 2 et un tableau de taille 3 contenant la chaˆıne "Bonjour" : # let q = make_vect 4 2;; q : int vect = [|2; 2; 2; 2|] # let r = make_vect 3 "Bonjour";; r : string vect = [|"Bonjour"; "Bonjour"; "Bonjour"|] La taille d’un tableau s’obtient en appelant la primitive vect_length. # vect_length q;; - : int = 4 Une fois le tableau cr´e´e, on peut consulter et modifier le contenu de ses cases. Si t est un tableau et n un entier, t.(n) d´esigne l’´el´ement d’indice n du tableau t. t t.(0) t.(n) t.(vect_length(t)−1) # let a0 = p.(0);; a0 : int = 3 On affecte la valeur v `a la case n du tableau t par la construction t.(n) <- v. Cela correspond graphiquement `a : t t.(0) t.(n − 1) t.(n + 1) t.(vect_length(t)−1) v La valeur retourn´ee par cette construction est (), la valeur « rien ». # q.(0) <- 1;; - : unit = () # q;; - : int vect = [|1; 2; 2; 2|] # r.(1) <- "tout"; r.(2) <- "le monde!";; - : unit = () # r;; - : string vect = [|"Bonjour"; "tout"; "le monde!"|] Nous savons maintenant d´efinir des tableaux, en lire et modifier les ´el´ements. Il nous faut encore apprendre `a les parcourir. C’est tr`es facile en utilisant les boucles que nous avons d´ecrites `a la section pr´ec´edente. Puisqu’il s’agit de parcourir un tableau, 42 Programmation imp´erative on connaˆıt `a l’avance le nombre de r´ep´etitions : on utilise donc une boucle « pour ». Le parcours complet d’un tableau t s’effectue par une boucle commen¸cant en 0 et finissant en vect_length t - 1. En effet, puisque les indices d’´el´ements de tableaux commencent toujours `a 0, le dernier ´el´ement d’un tableau a pour indice la longueur du tableau moins un. Par exemple : # for i = 0 to vect_length r - 1 do print_string r.(i) done;; Bonjourtoutle monde!- : unit = () Pour rendre la sortie plus jolie, il suffit d’ajouter un blanc apr`es chaque ´el´ement : # for i = 0 to vect_length r - 1 do print_string r.(i); print_string " " done;; Bonjour tout le monde! - : unit = () Syntaxe des tableaux Pour m´emoire, voici la syntaxe BNF correspondant `a ces deux constructions et `a la d´efinition des tableaux sous la forme de liste d’´el´ements. La syntaxe des d´efinitions de tableaux est la suivante : Tableaux ::= [| expression ( ; expression)∗ |] Nous utilisons ici un nouveau symbole pour la description des constructions syntaxiques qui acceptent les r´ep´etitions : l’´etoile « ∗ ». La formule quelque-chose∗ signifie la r´ep´etition de quelque-chose un nombre quelconque de fois, y compris z´ero fois si n´ecessaire (ce qui correspond alors `a ignorer compl`etement quelque-chose). Nous indiquons ainsi que le premier ´el´ement du tableau est ´eventuellement suivi d’autres ´el´ements, en nombre quelconque, s´epar´es par des points-virgules. La syntaxe de l’affectation et de l’acc`es aux ´el´ements de tableaux est la suivante : Acc`es dans un tableau ::= vect .( indice ) Modification d’un ´el´ement de tableau ::= vect .( indice ) <- expression Attention `a la signification des parenth`eses dans cette description. Elles font ici partie de la syntaxe d´ecrite (il faut les ´ecrire dans les programmes), alors que dans la notation ( ; expression)∗, les parenth`eses nous servaient `a regrouper les constructions syntaxiques « ; » et « expression ». (La diff´erence de nature des parenth`eses se traduit par un changement de police de caract`eres.) 3.4 Impression des polynˆomes Nous savons maintenant repr´esenter les polynˆomes `a l’aide des tableaux. Pour les manipuler, nous savons parcourir leurs coefficients `a l’aide d’une boucle for. Nous pouvons donc commencer l’impl´ementation des op´erations ´el´ementaires sur les polynˆomes. Commen¸cons par ´ecrire une proc´edure d’impression des polynˆomes pour visualiser simplement nos r´esultats. Il suffit de parcourir le tableau repr´esentant le polynˆome en imprimant ses monˆomes. Nous ´ecrivons donc d’abord la fonction d’impression d’un Impression des polynˆomes 43 monˆome de coefficient c et de degr´e d. C’est tr`es simple : si le degr´e est 0, il suffit d’´ecrire le coefficient ; sinon, on ´ecrit le coefficient et le degr´e sous la forme cX^d. Par exemple, 3x2 sera ´ecrit 3x^2. Cet imprimeur n’est pas tr`es ´elabor´e : il se contente de ne pas ´ecrire les monˆomes nuls ni les coefficients ´egaux `a 1 ; il traite aussi sp´ecialement le cas particulier des monˆomes de degr´e 0 et 1. Ainsi il ´ecrit x^2 pour le monˆome 1x2, 3 pour le monˆome 3x0 et 4x pour le monˆome 4x1. # let imprime_mon^ome coeff degr´e = if degr´e = 0 then print_int coeff else if coeff <> 0 then begin print_string " + "; if coeff <> 1 then print_int coeff; print_string "x"; if degr´e <> 1 then begin print_string "^"; print_int degr´e end end;; imprime_mon^ome : int -> int -> unit = La primitive <> correspond au pr´edicat math´ematique 6= et teste donc si deux valeurs sont diff´erentes. Il est temps de donner le nom technique des polynˆomes mod´elis´es par des tableaux d’entiers : on les appelle polynˆomes pleins, ce qui signifie simplement que leur repr´esentation comporte la liste exhaustive de leurs monˆomes, y compris ceux dont le coefficient est nul. C’est pourquoi nous appelons la fonction d’impression de ces polynˆomes imprime_polyn^ome_plein. Cette fonction se contente d’it´erer la proc´edure imprime_mon^ome sur tout le polynˆome, `a l’aide d’une boucle « pour ». # let imprime_polyn^ome_plein p = for i = 0 to vect_length p - 1 do imprime_mon^ome p.(i) i done;; imprime_polyn^ome_plein : int vect -> unit = Le polynˆome p = x2 + 2x + 3 s’imprime comme suit : # imprime_polyn^ome_plein p;; 3 + 2x + x^2- : unit = () Addition des polynˆomes L’addition des polynˆomes se fait monˆome par monˆome, en ajoutant les coefficients des monˆomes de mˆeme degr´e : (1 + 2x + 3x2) + (4 + 5x + 6x2) = (1 + 4) + (2 + 5)x + (3 + 6)x2 = 5 + 7x + 9x2. Math´ematiquement, on a : si P = p0 + p1x + p2x2 + · · · + pmxm et Q = q0 + q1x + q2x2 + · · · + qnxn, alors P + Q = (p0 + q0) + (p1 + q1)x + (p2 + q2)x2 + · · · + (pmax(m,n) + qmax(m,n))xmax(m,n) Cette d´efinition utilise la convention qu’un coefficient de degr´e sup´erieur au degr´e du polynˆome est implicitement 0. On peut exprimer ces formules de fa¸con plus synth´etique 44 Programmation imp´erative en introduisant la notation math´ematique  pour r´esumer les sommes de termes : si formule est une expression qui d´epend de l’entier i, on note Xn i=0 formule (i) pour formule (0) + formule (1) + · · · + formule (n). (La notation Pn i=0 formule (i) se lit « somme de i ´egale 0 `a n de formule (i) ».) Par exemple, si la formule est r´eduite `a i, on obtient la somme des nombres de 0 `a n : Xn i=0 i = 0 + 1 + · · · + n. De mˆeme, si la formule est i2, la somme correspondante est celle des carr´es des nombres entre 0 et n : Xn i=0 i2 = 02 + 12 + · · · + n2. On exprime ainsi de mani`ere tr`es concise deux polynˆomes g´en´eraux P et Q et leur somme P + Q: Si P = mX i=0 pi xi et Q = Xn i=0 qi xi alors P + Q = maxX(m,n) i=0 (pi + qi) xi. La traduction en Caml de cette d´efinition est tr`es simple : on cr´ee d’abord un tableau somme, pour contenir la somme des deux polynˆomes P et Q. Ce tableau a pour longueur le maximum des longueurs de P et Q, qu’on calcule avec la fonction pr´ed´efinie max. Il suffit de recopier les coefficients de l’un des polynˆomes dans les cases du r´esultat, puis d’y ajouter les coefficients de l’autre. # let ajoute_polyn^omes_pleins p q = let somme = make_vect (max (vect_length p) (vect_length q)) 0 in for i = 0 to vect_length p - 1 do somme.(i) <- p.(i) done; for i = 0 to vect_length q - 1 do somme.(i) <- somme.(i) + q.(i) done; somme;; ajoute_polyn^omes_pleins : int vect -> int vect -> int vect = Comme promis, nous pouvons maintenant calculer la valeur de 2x + 2x : # imprime_polyn^ome_plein (ajoute_polyn^omes_pleins [|0; 2|] [|0; 2|]);; 0 + 4x- : unit = () et v´erifier un de nos calculs pr´ec´edents : # imprime_polyn^ome_plein (ajoute_polyn^omes_pleins [|1; 2; 3|] [|4; 5; 6|]);; 5 + 7x + 9x^2- : unit = () et mˆeme (qui l’eˆut cru ?) calculer x − x et trouver 0 : # imprime_polyn^ome_plein (ajoute_polyn^omes_pleins [|0; 1|] [|0; -1|]);; 0- : unit = () Impression des polynˆomes 45 Multiplication des polynˆomes On d´efinit le produit des polynˆomes en utilisant les r`egles classiques de d´eveloppement des expressions alg´ebriques. En termes savants, on dit que l’on utilise la distributivit´e de la multiplication par rapport `a l’addition. Par exemple, pour tout polynˆome Q, on a (1 + 2x + 3x2) × Q = 1 × Q + 2x × Q + 3x2 × Q, et donc (1 + 2x + 3x2) × (4 + 5x + 6x2) = 1 × (4 + 5x + 6x2) + 2x × (4 + 5x + 6x2) + 3x2 × (4 + 5x + 6x2) = (4 + 5x + 6x2) + (8x + 10x2 + 12x3) + (12x2 + 15x3 + 18x4) = 4 + 13x + 28x2 + 27x3 + 18x4. La remarque fondamentale est que le produit des coefficients des monˆomes de degr´e i du premier polynˆome et de degr´e j du second forme une partie du coefficient du monˆome de degr´e i + j du produit. Par exemple, pour les monˆomes 2x et 6x2, le produit 2 × 6 entrera dans la composition du coefficient du monˆome de degr´e trois du r´esultat, ce qui signifie simplement que 2x × 6x2 = 12x3. Pour prendre en compte le produit des monˆomes 2x et 6x2 dans le r´esultat final, il suffit donc d’ajouter le produit 2×6 dans la case correspondant au coefficient de x3 du r´esultat final. Ainsi, notre proc´edure Caml va parcourir les monˆomes des deux polynˆomes deux `a deux, en les multipliant et en enregistrant le produit de leurs coefficients dans le monˆome de degr´e correspondant du produit. Pour les fanatiques des formules, ceci se traduit par la d´efinition math´ematique suivante : Le produit des deux polynˆomes P = mX i=0 pi Xi et Q = Xn j=0 qj Xj est le polynˆome (P × Q) = mX+n k=0 rk Xk avec rk = X i+j=k pi × qj , ou de fa¸con ´equivalente, (P × Q) = mX+n k=0 Xk i=0 pi × qk−i ! Xk. Remarquez que ces formules d’apparence r´ebarbative ne font que r´esumer de fa¸con tr`es succinte notre explication en fran¸cais, mais en aucun cas ne la rendent inutile. Pour coder la multiplication des polynˆomes en Caml, le plus difficile est de calculer la longueur du tableau r´esultat. On sait cependant que le monˆome de plus haut degr´e du r´esultat a pour degr´e la somme des degr´es des monˆomes de plus haut degr´e des polynˆomes multipli´es. Or, le degr´e du monˆome de plus haut degr´e d’un polynˆome repr´esent´e par un tableau v de longueur l est l − 1. C’est donc, en Caml, vect_length(v) − 1. Par exemple, le polynˆome p = x2 + 2x + 3, est repr´esent´e par un tableau `a trois cases et son monˆome de plus haut degr´e est de degr´e 2. Mais le monˆome de degr´e maximum du produit des polynˆomes p et q a pour degr´e la somme des degr´es des monˆomes de degr´e maximum de p et q, soit (vect_length(p) − 1) + (vect_length(q)−1). On en d´eduit facilement que le tableau repr´esentant p×q a pour longueur vect_length(p) + vect_length(q) − 1. Le programme Caml est bien plus court que ces explications : 46 Programmation imp´erative # let multiplie_polyn^omes_pleins p q = let produit = make_vect (vect_length p + vect_length q - 1) 0 in for i = 0 to vect_length p - 1 do for j = 0 to vect_length q - 1 do produit.(i + j) <- p.(i) * q.(j) + produit.(i + j) done done; produit;; multiplie_polyn^omes_pleins : int vect -> int vect -> int vect = Notre programme effectue sans peine le produit des polynˆomes (1 + 2x + 3x2) et (4 + 5x + 6x2) que nous avions pr´ec´edemment calcul´e `a la main. # imprime_polyn^ome_plein (multiplie_polyn^omes_pleins [|1; 2; 3|] [|4; 5; 6|]);; 4 + 13x + 28x^2 + 27x^3 + 18x^4- : unit = () Comme exemple plus consistant, calculons (x + 1)2 puis (x + 1)4 et (x + 1)8. # let p = [| 1; 1|] in let p2 = multiplie_polyn^omes_pleins p p in let p4 = multiplie_polyn^omes_pleins p2 p2 in let p8 = multiplie_polyn^omes_pleins p4 p4 in print_string "(x + 1) ** 2 = "; imprime_polyn^ome_plein p2; print_newline (); print_string "(x + 1) ** 4 = "; imprime_polyn^ome_plein p4; print_newline (); print_string "(x + 1) ** 8 = "; imprime_polyn^ome_plein p8; print_newline ();; (x + 1) ** 2 = 1 + 2x + x^2 (x + 1) ** 4 = 1 + 4x + 6x^2 + 4x^3 + x^4 (x + 1) ** 8 = 1 + 8x + 28x^2 + 56x^3 + 70x^4 + 56x^5 + 28x^6 + 8x^7 + x^8 - : unit = () 3.5 Caract`eres et chaˆınes de caract`eres En Caml les chaˆınes de caract`eres sont consid´er´ees comme des structures de donn´ees physiquement modifiables : les chaˆınes se comportent essentiellement comme des tableaux de caract`eres. On b´en´eficie ainsi d’un acc`es direct aux caract`eres et de la possibilit´e de modifier en place les caract`eres. La syntaxe de l’affectation et de l’acc`es aux caract`eres des chaˆınes est similaire `a celle des tableaux, avec des crochets [...] `a la place des parenth`eses (...) : Acc`es dans une chaˆıne s ::= s .[ indice ] Modification d’un caract`ere d’une chaˆıne s ::= s .[ indice ] <- expression Comme pour les tableaux, on parcourt donc compl`etement une chaˆıne de caract`eres par une boucle for commen¸cant en 0 et finissant `a la longueur de la chaˆıne moins 1, c’est-`a-dire string_length s−1. Par exemple, on calcule l’image miroir d’une chaˆıne en cr´eant une chaˆıne de mˆeme longueur dont on ´ecrit tous les caract`eres `a l’aide d’une boucle for : Les r´ef´erences 47 # let rev_string s = let l = string_length s in let res = make_string l ‘ ‘ in for i = 0 to l - 1 do res.[i] <- s.[l - 1 - i] done; res;; rev_string : string -> string = La primitive make_string permet de cr´eer une chaˆıne de caract`eres d’une longueur donn´ee et initialis´ee avec un caract`ere donn´e : c’est donc l’analogue de make_vect pour les chaˆınes de caract`eres. Au passage, cela donne une autre id´ee pour ´ecrire une version simple et lin´eaire de la fonction palindrome de la section 2.1 : # let palindrome s = let r = rev_string s in r = s;; palindrome : string -> bool = et mˆeme encore plus bri`evement : # let palindrome s = rev_string s = s;; palindrome : string -> bool = 3.6 Les r´ef´erences Les r´ef´erences sont des structures de donn´ees pr´ed´efinies qui mod´elisent les cases m´emoire de la machine. La propri´et´e caract´eristique des cases m´emoire est qu’on peut les lire et les ´ecrire : la lecture renvoie leur contenu courant, l’´ecriture change ce contenu. Les cases m´emoire sont utilis´ees pour repr´esenter des compteurs ou des accumulateurs, dont le contenu ´evolue au cours du calcul. Lire et ´ecrire les cases m´emoire Poursuivant l’analogie avec les cases m´emoire, vous pouvez vous figurer une r´ef´erence comme une boˆıte (la case m´emoire) qui contient une valeur : vous pouvez placer quelque chose dans la boˆıte (´ecriture), ou demander `a ouvrir la boˆıte pour examiner son contenu (lecture). Les r´ef´erences sont cr´e´ees `a l’aide de la construction ref(val), o`u val est la valeur initialement contenue dans la r´ef´erence. D´efinissons par exemple un compteur qui vaut initialement 0 : # let compteur = ref 0;; compteur : int ref = ref 0 La valeur de compteur est donc une boˆıte contenant 0, qu’on peut repr´esenter ainsi : compteur 0 Le contenu courant d’une r´ef´erence est renvoy´e par l’op´erateur de d´er´ef´erencement, c’est-`a-dire de lecture d’une r´ef´erence, not´e « ! ». (Il ne faut pas confondre cette notation avec l’op´erateur “factorielle” des math´ematiques, que nous avons vu au chapitre 2, et qui se place apr`es son argument ; le ! Caml se place avant son argument.) 48 Programmation imp´erative # !compteur;; - : int = 0 compteur 0 ! On change le contenu d’une r´ef´erence (´ecriture) en utilisant le symbole traditionnel de l’affectation « := ». # compteur := 2;; - : unit = () L’affectation est, graphiquement, l’op´eration inverse de « ! » : compteur 2 := Apr`es l’affectation, le contenu de la boˆıte compteur est donc 2. # !compteur;; - : int = 2 Pour incr´ementer compteur, nous devons ajouter 1 au contenu courant de la boˆıte : # compteur := 1 + !compteur;; - : unit = () # !compteur;; - : int = 3 Une r`egle g´en´erale en Caml est que tous les objets du langage sont manipulables comme des valeurs sans restrictions particuli`eres : on les passe en param`etre et on les rend en r´esultat, au mˆeme titre que les valeurs de base. Les r´ef´erences ne d´erogent pas `a cette r`egle. Nous pouvons donc passer des r´ef´erences en param`etre et d´efinir une proc´edure qui incr´emente le contenu de n’importe quelle r´ef´erence contenant un entier (cette proc´edure est pr´ed´efinie sous le nom incr dans le syst`eme Caml, mais nous en ´ecrivons le code `a titre d’exemple). La fonction prend une r´ef´erence c en argument et modifie son contenu (c := . . . ) pour y mettre la valeur courante de la r´ef´erence plus un (1 + !c) : # let incr´emente c = c := 1 + !c;; incr´emente : int ref -> unit = # incr´emente compteur; !compteur;; - : int = 4 Les variables imp´eratives Un identificateur li´e `a une r´ef´erence se comporte comme les variables des langages imp´eratifs (C, Pascal, Ada), puisqu’on peut modifier `a volont´e le contenu de la r´ef´erence. La seule diff´erence est qu’en Caml il faut explicitement d´er´ef´erencer l’identificateur `a l’aide de l’op´erateur !, pour en obtenir la valeur courante ; la distinction entre l’objet variable et la valeur courante de cet objet est donc plus nette. Comme nous l’avions annonc´e `a la section 1.3, la d´efinition d’un nom par la construction let est diff´erente de l’affectation d’une variable. Nous sommes maintenant en mesure de comprendre cette diff´erence, en comparant la red´efinition d’un identificateur par un nouveau let et l’affectation d’un identificateur li´e `a une r´ef´erence : Un programme utilisant des r´ef´erences 49 # let x = 1;; x : int = 1 # let f y = x + y;; f : int -> int = # let x = 2;; x : int = 2 # f 0;; - : int = 1 # let x = ref 1;; x : int ref = ref 1 # let f y = !x + y;; f : int -> int = # x := 2;; - : unit = () # f 0;; - : int = 2 Dans la colonne de gauche, la red´efinition de x ne modifie en rien la valeur de x dans le corps de la fonction f. En revanche `a droite, l’identificateur x est li´e `a une r´ef´erence. La valeur de !x dans le corps de f change donc ´evidemment apr`es l’affectation (cependant x est toujours li´e `a la mˆeme valeur : la mˆeme r´ef´erence). On constate ainsi que les fonctions qui utilisent des r´ef´erences non locales sont susceptibles de changer dynamiquement de comportement, au gr´e des affectations des r´ef´erences qu’elles emploient. 3.7 Un programme utilisant des r´ef´erences Un exemple r´ealiste d’utilisation des r´ef´erences nous est fourni par la fonction « factorielle », qui retourne le produit des nombres entiers inf´erieurs ou ´egaux `a son argument. Nous en avions donn´e la d´efinition r´ecursive suivante au chapitre 2 : n! = ( 1 si n = 0 n × (n − 1)! sinon. Voici une autre d´efinition, dont nous admettrons qu’elle est math´ematiquement ´equivalente : 0! = 1 n! = 1 × 2 × · · · × (n − 1) × n Cette d´efinition avec trois petits points « . . . » est allusive et se traduit g´en´eralement par une impl´ementation sur machine `a base de boucles et d’accumulateurs. Ainsi, on d´efinira une r´ef´erence pour accumuler les multiplications par les nombres plus petits que n, durant l’ex´ecution d’une boucle allant de 1 `a n. `A chaque tour on multiplie le contenu actuel de l’accumulateur par l’indice de boucle courant (accu := i * !accu), si bien qu’`a la fin de la boucle l’accumulateur contient le r´esultat voulu ; on renvoie donc son contenu (!accu). # let fact n = if n = 0 then 1 else begin let accu = ref 1 in for i = 1 to n do accu := i * !accu done; !accu end;; fact : int -> int = 50 Programmation imp´erative # fact 10;; - : int = 3628800 Une petite remarque : on peut supprimer le test if n = 0 sans changer la s´emantique de la fonction. En effet, lorsque n est nul, la boucle s’arrˆete instantan´ement, alors que la r´ef´erence accu vaut 1, ce qui est le r´esultat correct dans ce cas. On obtient plus simplement : # let fact n = let accu = ref 1 in for i = 1 to n do accu := i * !accu done; !accu;; fact : int -> int = Cet exemple nous permet de comparer `a meilleur escient les styles imp´eratif et fonctionnel. En effet, nous avons dˆu indiquer `a la machine la suite des op´erations `a effectuer, en g´erant explicitement les modifications successives du contenu de l’accumulateur accu : il s’agit vraiment ici d’un programme de style imp´eratif. En revanche, souvenezvous de notre pr´ec´edente impl´ementation r´ecursive de la fonction factorielle : # let rec factorielle = function | 0 -> 1 | n -> n * factorielle (n - 1);; factorielle : int -> int = Dans ce cas, on a presque l’impression d’avoir recopi´e la d´efinition math´ematique ! Vous comprenez ais´ement que le style imp´eratif est plus descriptif du calcul `a effectuer que le style fonctionnel. Le style imp´eratif d´ecrit comment faire le calcul, tandis que le style fonctionnel d´ecrit quoi calculer. On dit que le style fonctionnel est davantage d´eclaratif. En effet, en ´ecrivant la version fonctionnelle de fact, nous n’avons pas d´ecrit comment faire : c’est le compilateur qui a g´er´e lui-mˆeme l’enchaˆınement des calculs. 3.8 R´ecursivit´e et boucles Cette section montre qu’une d´efinition r´ecursive peut cacher une boucle et qu’on peut d´efinir une fonction r´ecursive localement `a une autre fonction. Rappelons la d´efinition de la fonction ´epelle : # let rec ´epelle_aux s i = if i < string_length s then begin print_char s.[i]; print_char ‘ ‘; ´epelle_aux s (i + 1) end;; ´epelle_aux : string -> int -> unit = # let ´epelle s = ´epelle_aux s 0;; ´epelle : string -> unit = Comme dans le cas des palindromes, si vous estimez que la fonction auxiliaire ´epelle_aux n’a pas d’int´erˆet en soi, puisqu’elle ne sert qu’`a d´efinir ´epelle, rien ne vous empˆeche de la d´efinir localement `a l’int´erieur de la fonction ´epelle : R´ecursivit´e et boucles 51 # let ´epelle s = let rec ´epelle_aux s i = if i < string_length s then begin print_char s.[i]; print_char ‘ ‘; ´epelle_aux s (i + 1) end in ´epelle_aux s 0;; ´epelle : string -> unit = # ´epelle "Bonjour";; B o n j o u r - : unit = () Si l’on remarque alors que la chaˆıne s ne varie jamais pendant les appels `a la fonction ´epelle_aux, on la supprime purement et simplement des arguments de ´epelle_aux (car s est li´ee `a l’ext´erieur de ´epelle_aux par la fonction ´epelle). On obtient maintenant # let ´epelle s = let rec ´epelle_aux i = if i < string_length s then begin print_char s.[i]; print_char ‘ ‘; ´epelle_aux (i + 1) end in ´epelle_aux 0;; ´epelle : string -> unit = On constate alors que la fonction ´epelle_aux n’est autre qu’une boucle for d´eguis´ee : son premier argument est 0, son dernier argument string_length s − 1, et ´epelle_aux augmente l’indice i de 1 `a chaque appel r´ecursif. On r´e´ecrit donc facilement ´epelle avec une boucle for : # let ´epelle s = for i = 0 to string_length s - 1 do print_char s.[i]; print_char ‘ ‘ done;; ´epelle : string -> unit = # ´epelle "Caml";; C a m l - : unit = () C’est ´evidemment le cas aussi pour compte et compte_`a_rebours : # let compte n = for i = 1 to n do print_int i; print_string " " done;; compte : int -> unit = # let compte_`a_rebours n = for i = n downto 1 do print_int i; print_string " " done;; compte_`a_rebours : int -> unit = # compte 10; compte_`a_rebours 10;; 1 2 3 4 5 6 7 8 9 10 10 9 8 7 6 5 4 3 2 1 - : unit = () On peut donc h´esiter entre les deux styles de programmation. Chaque fois qu’une d´efinition r´ecursive code une boucle for, il faut sans conteste employer une boucle : les indices sont en tˆete de boucle, il n’y a pas besoin de fonction auxiliaire, c’est donc bien plus clair. En ce qui concerne l’efficacit´e, il n’est pas clair que l’une des versions 52 Programmation imp´erative soit meilleure que l’autre : tout d´epend du compilateur. Remarquez en particulier que les versions r´ecursives ne font que des appels r´ecursifs terminaux, c’est-`a-dire plac´es en fin de fonction. Ce genre d’appels r´ecursifs est tr`es bien optimis´e par les compilateurs Caml, qui les transforment automatiquement en boucles. Le processus de r´e´ecriture d’une fonction r´ecursive `a l’aide de boucles, que nous avons vu ici sur des exemples, s’appelle la « d´er´ecursivation ». Cette op´eration est souvent difficile ; essayez par exemple de d´er´ecursiver la fonction hanoi. A contrario, les fonctions r´ecursives permettent d’´ecrire facilement les boucles les plus complexes, en particulier lorsque la boucle comporte plusieurs sorties possibles ou lorsque l’indice ne varie pas de fa¸con uniforme, ou simplement lorsqu’il faut retourner une valeur significative (diff´erente de ()) `a la fin de la boucle. Cependant la r´ecursivit´e ne se limite certainement pas `a coder des boucles : c’est un outil extrˆemement puissant, comme vous l’avez constat´e avec le jeu de Hanoi. Nous en verrons encore beaucoup d’exemples non triviaux dans la suite. 3.9 R`egle d’extensionnalit´e Il nous faut faire ici une remarque importante, que nous utiliserons quelques fois. Il s’agit d’un exemple de r`egle de calcul sur les programmes qui permet `a l’occasion de simplifier les programmes. Ce genre de r`egles constitue l’´equivalent informatique des r`egles de simplification des math´ematiques. Cette r`egle est tr`es facile : elle stipule qu’il est inutile de d´efinir une fonction qui se contente d’en appeler imm´ediatement une autre. # let successeur x = x + 1;; successeur : int -> int = # let succ = function x -> successeur x;; succ : int -> int = La fonction succ applique imm´ediatement la fonction successeur `a son argument x ; elle retournera donc forc´ement toujours les mˆemes valeurs que la fonction successeur : c’est la mˆeme fonction que successeur. On dit que la fonction succ est ´egale `a la fonction successeur. D’ailleurs, on aurait tr`es bien pu la d´efinir par : # let succ = successeur;; succ : int -> int = Plus g´en´eralement on a, Pour toute fonction f, function x -> f x est ´equivalent `a f De la mˆeme fa¸con qu’en math´ematiques les r`egles du calcul alg´ebrique permettent de remplacer x − x par 0, nous utiliserons la r`egle pr´ec´edente pour ´ecrire successeur au lieu du programme function x -> successeur x. Nous aurons ainsi simplifi´e notre programme en utilisant une r`egle universelle, sans mˆeme avoir besoin de r´efl´echir au contenu du programme. On se convainc facilement de la validit´e de la r`egle pr´ec´edente en remarquant que les deux fonctions retournent toujours le mˆeme r´esultat quel que soit leur argument. En effet, (function x -> f x) (y) s’´evalue comme f y (en rempla¸cant x par y dans Effets et ´evaluation 53 f x). On peut ´egalement consid´erer que cette r`egle est la simple traduction de la d´efinition math´ematique des fonctions : la fonction f est celle qui `a tout ´el´ement x de son ensemble de d´epart fait correspondre son image par f, c’est-`a-dire f (x). La fonction f est donc la correspondance x 7→ f(x), c’est exactement ce que traduit la r`egle « f = function x -> f x ». Cette r`egle est appel´ee « r`egle d’extensionnalit´e » ou encore « r`egle  » (la lettre grecque  se prononce « ˆeta »). Quand on remplace f par l’expression plus complexe function x -> f x, on dit qu’on fait une -expansion. Inversement, quand on simplifie function x -> f x en f, on fait une -contraction. La plupart du temps, nous emploierons la r`egle  dans le sens de la simplification (-contraction). Les conventions syntaxiques de d´efinition et d’application des fonctions se combinent avec la r`egle  pour ´etablir que : let g x = f x ; ; est ´equivalent `a let g = f ; ; En effet, la convention syntaxique des d´efinitions de fonctions nous permet d’´ecrire : let g = function x -> f x au lieu de let g x = f x ; ; Puis, en appliquant la r`egle , on simplifie function x -> f x en f et l’on obtient : let g = f ; ; Vous pouvez constater que nous avons ici raisonn´e sur un petit programme ind´ependamment de sa signification : on ne sait pas ce que fait f ni pourquoi on d´efinit g. C’est une force de connaˆıtre ce genre de raisonnements tr`es g´en´eraux qui s’appliquent dans toutes les situations. Nous n’irons pas plus loin dans ces consid´erations un peu complexes et formelles. La r`egle  restera notre seule r`egle de calcul sur les programmes. Son nom grec ne doit pas effrayer, car la r`egle  est intuitivement tr`es naturelle : elle stipule simplement que la fonction qui `a x associe f(x) est la fonction f, et r´eciproquement. 3.10 Effets et ´evaluation Cette section peut ˆetre saut´ee en premi`ere lecture. Effets et appels de fonctions Avec les effets, nous sortons du monde intemporel des math´ematiques pour entrer dans un monde qui connaˆıt les notions de pass´e et d’avenir et dans lequel l’enchevˆetrement des calculs a son importance. Pour calculer une expression sans effets comme 1 * 2 + 3 * 4, peu importe l’ordre dans lequel on effectue les calculs : qu’on commence par calculer 1 * 2 ou 3 * 4, le r´esultat est identique. Il n’en va pas de mˆeme si l’on m´elange calculs et effets. Pour mettre ce ph´enom`ene en ´evidence, ajoutons des effets dans le calcul de 1 * 2 + 3 * 4 en y mˆelant des impressions au terminal. Rempla¸cons d’abord les nombres par des s´equences qui les impriment, par exemple rempla¸cons 1 par (print_int 1; 1). (On doit obligatoirement parenth´eser une s´equence pour l’inclure dans une op´eration.) Puis effectuons le calcul en commen¸cant par l’une ou l’autre de ses sous-expressions : d’abord 1 * 2, puis 3 * 4. 54 Programmation imp´erative # let un_fois_deux = (print_int 1; 1) * (print_int 2; 2) in let trois_fois_quatre = (print_int 3; 3) * (print_int 4; 4) in un_fois_deux + trois_fois_quatre;; 2143- : int = 14 # let trois_fois_quatre = (print_int 3; 3) * (print_int 4; 4) in let un_fois_deux = (print_int 1; 1) * (print_int 2; 2) in un_fois_deux + trois_fois_quatre;; 4321- : int = 14 On n’obtient ´evidemment pas les mˆemes impressions `a l’´ecran. Laissons le compilateur nous d´evoiler l’ordre qu’il choisit : # (print_int 1; 1) * (print_int 2; 2) + (print_int 3; 3) * (print_int 4; 4);; 4321- : int = 14 Un autre compilateur aurait pu choisir un autre ordre. La conclusion de cette exp´erience est qu’il ne faut jamais m´elanger effets et appels de fonctions, car on ne sait pas alors d´eterminer le moment o`u les effets vont intervenir. En effet, l’ordre d’´evaluation des arguments d’une fonction n’est pas garanti par le langage. Seules la s´equence, l’alternative et la construction let ont un ordre d’´evaluation d´etermin´e. Ce n’est pas ´etonnant pour la s´equence, puisque c’est son rˆole de fixer l’ordre d’´evaluation de deux expressions. Pour l’alternative, il est clair qu’on ne peut d´ecider la branche `a choisir qu’apr`es avoir ´evalu´e la condition. En ce qui concerne le let, on ´evalue toujours l’expression d´efinissante d’abord : dans let x = e1 in e2 on ´evalue e1 avant e2, garantissant ainsi que la valeur de l’identificateur x est connue pendant le calcul de l’expression e2. Effets et r`egle  La r`egle  du paragraphe pr´ec´edent stipule que l’expression (function x -> f x) est ´equivalente `a f. Nous avons pr´ecis´e que cette r`egle s’applique quand f est une fonction. En fait, cette r`egle s’´etend facilement au cas o`u f est une expression quelconque, mais seulement dans le cas o`u cette expression f ne produit pas d’effets. Insistons : la r`egle est parfaitement correcte pour toute fonction f, que f produise des effets ou non. De plus cette r`egle s’´etend `a toute expression, pourvu que cette expression ne produise aucun effet. Il est pourtant impossible d’´etendre la r`egle `a une expression quelconque, car certaines expressions produisant des effets l’invalident. Il est si tentant d’utiliser cette r`egle ´etendue sans v´erifier que l’expression impartie est sans effets que nous pensons n´ecessaire d’´etudier un exemple o`u les effets empˆechent de l’utiliser sous peine d’erreur. D´efinissons une fonction f qui incr´emente son argument, puis retourne une fonction en r´esultat : # let f x = incr x; (function z -> z + 1);; f : int ref -> int -> int = Puis nous d´efinissons une autre fonction, g, qui appelle f avec la variable d´ej`a d´efinie compteur. Nous prenons ´egalement bien soin de suivre l’´evolution du contenu de la r´ef´erence compteur. # !compteur;; - : int = 4 Effets et ´evaluation 55 # let g y = f compteur y;; g : int -> int = # !compteur;; - : int = 4 On constate que la d´efinition de g n’a pas modifi´e la valeur de compteur, ce qui semble normal. En revanche, chaque fois qu’on appelle la fonction g avec une valeur v, on ´evalue l’expression f compteur v, si bien que la valeur de compteur est incr´ement´ee `a chaque appel de g, ce qui semble toujours un comportement raisonnable. # g 0;; - : int = 1 # !compteur;; - : int = 5 Mais supposez maintenant que nous utilisions la r`egle  pour simplifier la d´efinition de g en supprimant l’argument y. C’est bien sˆur interdit, car nous utiliserions la r`egle avec l’expression « f compteur » qui n’est pas une fonction (c’est une application) et qui de surcroˆıt produit des effets. Pour montrer que le comportement du programme changerait, faisons-le tout de mˆeme, en suivant toujours soigneusement l’´evolution de la valeur de compteur. # let g = f compteur;; g : int -> int = # !compteur;; - : int = 6 On constate que la valeur de compteur a ´et´e modifi´ee en d´efinissant g. En effet, pour d´efinir g on applique maintenant f `a compteur et cela produit tout naturellement un effet sur compteur pendant la d´efinition de g, puisque l’´evaluation de f compteur provoque l’ex´ecution de la s´equence incr x; (function z -> z + 1) o`u x est li´e `a compteur. On incr´emente donc compteur et l’on renvoie la fonction. Maintenant, appelons g comme pr´ec´edemment : # !compteur;; - : int = 6 # g 0;; - : int = 1 # !compteur;; - : int = 6 La valeur de compteur n’est plus modifi´ee `a l’appel de g. En effet, g a maintenant pour valeur la fonction function z -> z + 1 qui ne fait bien sˆur aucun effet sur la valeur de compteur. Par la suite, les appels `a g n’incr´ementeront donc plus jamais compteur. Ce comportement est dˆu `a l’effet retard induit par les param`etres de fonction : quand on d´efinit g avec un argument explicite y, il faut attendre l’application de g `a une valeur pour commencer l’ex´ecution du corps de g, donc l’´evaluation de f compteur y. Dans le cas d’une fonction d´efinie sans argument (par un calcul), les effets interviennent imm´ediatement puisqu’il n’y a aucune valeur de param`etre `a attendre. 56 Programmation imp´erative 4 Fonctionnelles et polymorphisme O`u l’on apprend qu’il y a des fonctions qui fabriquent des fonctions et des types qui remplacent n’importe quoi. uoique r´eput´ees difficiles, les notions de polymorphisme et de pleine fonctionnalit´e s’introduisent tr`es naturellement en Caml et vous constaterez dans ce chapitre qu’elles ne sont pas si ardues. Si vous n’avez jamais entendu parler de polymorphisme, lisez la premi`ere section. Les sections suivantes, jusqu’`a la section 4.7, montrent le polymorphisme `a l’oeuvre dans des situations pratiques. Au passage (section 4.3), nous ferons le point sur l’alg`ebre de types de Caml. 4.1 Notion de polymorphisme ´E tymologiquement, polymorphe signifie plusieurs (poly) formes (morphe). On emploie ce mot par exemple en psychologie pour parler de pervers polymorphes (pervers qui ne sont pas fix´es sur une forme pr´ecise de perversion, ce qui est un stade normal de d´eveloppement psychologique de l’enfant), ou bien en m´edecine pour des maladies qui entraˆınent des symptˆomes variables, ou des virus dont l’aspect varie. En informatique, ce terme d´esigne des objets ou des programmes qui peuvent servir sans modifi- cations dans des contextes tr`es divers. Par exemple, une fonction de tri d’objets sera monomorphe si elle ne s’applique qu’`a un seul type d’objets (par exemple les entiers) et polymorphe si elle s’applique `a tous les types d’objets qu’on peut comparer pour les ranger du plus petit au plus grand. Dans ce dernier cas, le mˆeme programme de tri s’appliquera sans modifications `a des entiers (comparaison ≤), `a des nombres flottants (comparaison ≤ des flottants) et `a des chaˆınes de caract`eres (ordre du dictionnaire). Du point de vue du typage, cela signifie que la fonction de tri pourra ˆetre employ´ee avec plusieurs types diff´erents. Le polymorphisme n’est pas l’apanage des fonctions : certaines valeurs non fonctionnelles peuvent aussi ˆetre utilis´ees avec plusieurs « formes », c’est-`a-dire plusieurs types. Les exemples se trouvent du cˆot´e des structures de donn´ees comme les tableaux et les listes : clairement, un tableau de nombres entiers ne pourra pas ˆetre employ´e avec un autre type, mais vous admettrez facilement que le tableau vide (le tableau `a z´ero ´el´ement) peut ˆetre vu comme un tableau d’entiers aussi bien que comme un tableau 58 Fonctionnelles et polymorphisme de chaˆınes. Nous allons nous int´eresser d’abord aux fonctions polymorphes, parce que c’est dans le domaine des fonctions que cette notion est la plus naturelle et la plus facile `a appr´ehender. Pour exprimer le polymorphisme dans les expressions de types, nous avons besoin d’une notion de types qui puissent remplacer plusieurs types diff´erents : ce sont les param`etres de type, qu’on distingue syntaxiquement des types ordinaires en les faisant pr´ec´eder d’une apostrophe (’). Par exemple, ’a est un param`etre de type nomm´e a. Le polymorphisme de Caml est techniquement qualifi´e de param´etrique. Intuitivement cela signifie que ce polymorphisme fonctionne en « tout ou rien ». La signification d’un param`etre de type est de remplacer n’importe quel autre type et non pas un certain nombre d’autres types. On n’aura donc pas de programmes Caml uniquement valables pour un ensemble d´etermin´e de types. Par exemple, il n’y a aucun moyen de d´efinir une fonction qui s’appliquerait uniquement `a des entiers et des chaˆınes de caract`eres (et qui aurait donc un type du genre (int ou string) -> ...) Un programme Caml s’applique soit `a tous les types possibles, soit `a un seul et unique type. Dans le premier cas le type du programme comporte un param`etre (par exemple ’a -> ...), dans le second cas il n’en comporte pas (par exemple int -> ...). Voyons un premier exemple : # let successeur x = x + 1;; successeur : int -> int = La fonction est monomorphe, comme on s’y attend : elle ne s’applique qu’`a des entiers, puisqu’on doit faire une addition avec son argument. Mais supposons qu’on supprime l’addition qui entraˆıne cette contrainte sur l’argument x et qu’on renvoie directement 1. # let fonction_un x = 1;; fonction_un : ’a -> int = La fonction fonction_un est maintenant polymorphe : elle ne fait rien de son argument, on peut donc l’appliquer `a n’importe quoi. # fonction_un 2;; - : int = 1 # fonction_un "oui";; - : int = 1 # fonction_un true;; - : int = 1 Contrairement `a ce que sugg`ere l’exemple fonction_un, une fonction polymorphe peut utiliser son argument, par exemple en le renvoyant tel quel. Nous supprimons encore une fois l’addition dans le code de successeur, mais cette fois nous renvoyons x au lieu de 1 : # let identit´e x = x;; identit´e : ’a -> ’a = Nous obtenons encore une fonction polymorphe. Notez que le type de la fonction identit´e indique `a juste titre que le type du r´esultat est exactement celui de l’argument. Le param`etre ’a remplace n’importe quel type, en particulier string ou int, l´egitimant ainsi l’emploi de identit´e avec le type string -> string, et aussi avec le type int -> int : # identit´e "non";; - : string = "non" # identit´e 1;; - : int = 1 Fonctions d’ordre sup´erieur 59 Ce m´ecanisme de remplacement d’un param`etre de type par un type quelconque s’appelle la sp´ecialisation. Nos deux exemples consistent donc `a sp´ecialiser ’a en string, puis en int. On n’est pas oblig´e de sp´ecialiser un param`etre avec un type de base, comme nous l’avons fait jusqu’`a pr´esent ; on le sp´ecialise tout aussi bien avec un type complexe, par exemple int -> int. Dans le cas de la fonction identit´e, on obtient le type (int -> int) -> (int -> int). Cela sugg`ere d’appeler la fonction identit´e sur un argument qui est lui-mˆeme une fonction ; et pourquoi pas la fonction successeur ? # identit´e successeur;; - : int -> int = La fonction identit´e renvoie toujours son argument sans modification ; elle renvoie donc tout simplement la fonction successeur quand on l’applique `a successeur. Par exemple : # let success = identit´e successeur;; success : int -> int = # success 3;; - : int = 4 4.2 Fonctions d’ordre sup´erieur Les fonctions d’ordre sup´erieur sont des fonctions dont les arguments ou les r´esultats sont eux-mˆemes des fonctions. Une fonction d’ordre sup´erieur est encore appel´ee une fonctionnelle. Ces fonctions sont souvent polymorphes et surtout employ´ees avec des structures de donn´ees plus complexes que les types de base. Il n’est pas n´ecessaire de tout connaˆıtre sur le polymorphisme et les fonctions d’ordre sup´erieur pour les utiliser. Pour le lecteur plus int´eress´e par les programmes que par la th´eorie, il suffit donc de lire cette section pour comprendre le reste des exemples de ce livre. Nous commencerons par les fonctions qui renvoient d’autres fonctions, puis nous verrons les fonctions dont les arguments sont fonctionnels. Les exemples pr´esent´es ici seront sans doute un peu artificiels, puisque nous ne disposons pas encore des outils suffisants pour montrer des utilisations r´ealistes de fonctionnelles sur des structures de donn´ees complexes. Fonctions retournant des fonctions Supposez que nous voulions d´efinir la fonction fois_x qui, ´etant donn´e un entier x, fabrique la fonction qui multipliera par x. La d´efinition en pseudo-code Caml serait : let fois_x (x) = « la fonction qui multiplie par x »;; Nous ´ecrivons donc du vrai code pour la p´eriphrase « la fonction qui multiplie par x » : # let fois_x (x) = (function y -> x * y);; fois_x : int -> int -> int = La fonction fois_x est une fonctionnelle : quand on l’applique `a un argument elle fabrique une fonction qu’elle renvoie en r´esultat ! Ainsi, on obtient la multiplication par 2 en appliquant fois_x `a 2. 60 Fonctionnelles et polymorphisme # let double = fois_x (2);; double : int -> int = # double (3);; - : int = 6 On constate ainsi qu’on a d´efini la fonction double non pas en donnant les arguments et le corps de la fonction (par la construction function), mais par un calcul : nous avons calcul´e une fonction. Il n’est d’ailleurs mˆeme pas n´ecessaire de donner un nom `a la fonction double pour calculer double (3) : nous pouvons directement appliquer la fonction calcul´ee fois_x (2) `a la valeur 3. # (fois_x (2)) (3);; - : int = 6 La syntaxe de Caml autorise une ´ecriture encore simplifi´ee de cette expression, sans aucune parenth`ese. Rappelons que les conventions syntaxiques pour l’application des fonctions en Caml stipulent en effet que • f x est lu comme f (x) • f x y est lu comme (f x) y. En employant la premi`ere r`egle on obtient d’abord (fois_x 2) 3, puis en appliquant la seconde, on obtient la forme la plus simple fois_x 2 3. # fois_x 2 3;; - : int = 6 Fonctions dont les arguments sont des fonctions Faisons un pas de plus : d´efinissons une fonction avec un argument fonctionnel. Par exemple, la fonctionnelle double_le_r´esultat_de qui double le r´esultat de sa fonction argument : ´etant donn´es une fonction f et un argument x pour f, double_le_r´esultat_de retourne la valeur 2 * f(x), c’est-`a-dire double (f x). # let double_le_r´esultat_de (f : int -> int) = function x -> double (f x);; double_le_r´esultat_de : (int -> int) -> int -> int = Avec cette fonctionnelle nous pouvons `a nouveau calculer des fonctions. Par exemple, la fonction qui retourne le double du successeur de son argument, function x -> 2 * (x + 1), se calcule en appliquant la fonctionnelle double_le_r´esultat_de `a la fonction successeur. C’est un calcul tout `a fait normal, si ce n’est qu’il implique des fonctions et que le r´esultat est en fait une fonction. # let double_du_successeur = double_le_r´esultat_de successeur;; double_du_successeur : int -> int = # double_du_successeur 3;; - : int = 8 Typage et polymorphisme 61 La fonctionnelle « sigma » `A l’occasion de la d´efinition des op´erations sur les polynˆomes, nous avons vu la notation math´ematique , d´efinie par : Xn i=0 formule (i) = formule (0) + formule (1) + · · · + formule (n). Avec le vocabulaire des langages fonctionnels, la notation  n’est rien d’autre qu’une fonctionnelle qui prend en argument une fonction repr´esentant formule et l’applique successivement `a 0, 1, . . . , n. Il est tr`es facile de d´efinir cette fonctionnelle en Caml, `a l’aide d’une boucle et d’un accumulateur : # let sigma formule n = let r´esultat = ref 0 in for i = 0 to n do r´esultat := !r´esultat + formule (i) done; !r´esultat;; sigma : (int -> int) -> int -> int = ou mˆeme `a l’aide d’une fonction r´ecursive # let rec sigma formule n = if n <= 0 then 0 else formule n + sigma formule (n - 1);; sigma : (int -> int) -> int -> int = Nous pouvons maintenant faire calculer par Caml les exemples que nous avions donn´es. Si la formule est r´eduite `a i, on obtient la somme des nombres de 0 `a n : Xn i=0 i = 0 + 1 + · · · + n. Cette formule correspond `a appeler la fonctionnelle sigma avec l’argument (function i -> i). # sigma (function i -> i) 10;; - : int = 55 # sigma identit´e 10;; - : int = 55 De mˆeme la somme des carr´es des nombres entre 0 et n, Xn i=0 i2 = 02 + 12 + · · · + n2, s’obtient par application de sigma `a l’argument (function i -> i * i). # sigma (function i -> i * i) 10;; - : int = 385 4.3 Typage et polymorphisme Synth`ese du type le plus g´en´eral Comme nous l’avons d´ej`a dit, le compilateur de Caml donne un type `a chaque phrase entr´ee par l’utilisateur ; cette inf´erence de types ne n´ecessite aucune participation de 62 Fonctionnelles et polymorphisme l’utilisateur : elle se produit automatiquement sans n´ecessit´e d’indiquer les types dans les programmes. Connaissant les types des valeurs de base et des op´erations primitives, le contrˆoleur de types produit un type pour une phrase en suivant des r`egles de typage pour les constructions du langage comme la d´efinition et l’application des fonctions. De plus, le type inf´er´e contient le plus petit ensemble de contraintes n´ecessaires au bon d´eroulement de l’ex´ecution du programme (ici, « bon d´eroulement » signifie qu’il n’y aura pas d’erreurs de type `a l’ex´ecution). On dit que le contrˆoleur de type trouve le type le plus g´en´eral de chaque expression (notion introduite par Robin Milner en 1978). Par exemple, la fonction successeur re¸coit le type int -> int parce que son argument doit ˆetre un entier, puisqu’on lui ajoute 1. En revanche la fonction identit´e a le type ’a -> ’a parce qu’il n’y a pas de contrainte sur son argument. Le polymorphisme s’introduit donc naturellement `a partir de l’absence de contraintes sur le type d’un argument ou d’une valeur. Par exemple, rappelons la d´efinition de la fonctionnelle double_le_r´esultat_de : # let double_le_r´esultat_de (f : int -> int) = function x -> double (f x);; double_le_r´esultat_de : (int -> int) -> int -> int = L’argument f devait ˆetre une fonction des entiers vers les entiers, `a cause de la contrainte de type (f : int -> int), explicitement ´ecrite dans le programme. Mais si nous retirons cette contrainte de type, nous obtenons une fonctionnelle plus g´en´erale : # let double_le_r´esultat_de f = function x -> double (f x);; double_le_r´esultat_de : (’a -> int) -> ’a -> int = La fonctionnelle devient polymorphe, car le contrˆoleur de type a d´ecouvert que f devait seulement renvoyer un entier en r´esultat, mais qu’il n’est nullement obligatoire qu’elle prenne un entier en argument. Voici un exemple o`u f re¸coit une chaˆıne de caract`eres : # let double_de_la_longueur = double_le_r´esultat_de string_length;; double_de_la_longueur : string -> int = # double_de_la_longueur "Caml";; - : int = 8 Le polymorphisme d´ecoule donc de l’absence de contraintes sur une valeur. Cela explique pourquoi un param`etre de type peut ˆetre remplac´e sans risque d’erreurs par n’importe quel type, y compris un type lui-mˆeme polymorphe. Par exemple, on applique la fonction identit´e `a elle-mˆeme en l’employant avec le type (’a -> ’a) -> (’a -> ’a) : # let id x = (identit´e identit´e) x;; id : ’a -> ’a = Puisque la fonction identit´e renvoie toujours son argument, (identit´e identit´e) s’´evalue en identit´e, et la fonction id est donc tout simplement ´egale `a la fonction identit´e. L’alg`ebre des types de Caml Nous allons maintenant pr´eciser davantage l’ensemble des types qu’utilise le syst`eme Caml, ce qu’on nomme techniquement son alg`ebre des types. Tout type Caml entre dans l’une des cat´egories suivantes : Typage et polymorphisme 63 • Types de base (comme int ou string). • Types composites (comme int -> int ou int vect). • Param`etres de type (comme ’a). Les types composites sont construits avec des constructeurs de types, tels que la fl`eche ->. ´Etant donn´es deux types t1 et t2, le constructeur de type fl`eche construit le type t1 -> t2, qui est le type des fonctions ayant un argument du type t1 et rendant un r´esultat du type t2, autrement dit les fonctions de t1 dans t2. Remarquons que le constructeur fl`eche est un op´erateur binaire (deux arguments) et infixe (situ´e entre ses arguments, comme l’est le symbole de l’addition +). En revanche, le constructeur de types vect est unaire, puisqu’`a partir d’un unique type t1, il construit le type t1 vect. Ce constructeur est postfixe, c’est-`a-dire plac´e apr`es son argument. Tous les constructeurs de types unaires sont postfix´es en Caml. Par extension, les types n’ayant pas d’arguments (int par exemple) sont appel´es constructeurs de types constants. Les paires Il existe un autre constructeur de type binaire et infixe dont nous n’avons pas encore parl´e : le constructeur pr´ed´efini « * ». ´Etant donn´es deux types t1 et t2, la notation t1 * t2 est donc un type. C’est le produit cart´esien des types t1 et t2. Il d´enote le type des couples d’un ´el´ement du type t1 avec un ´el´ement du type t2. En math´ematiques, le produit cart´esien de deux ensembles A et B est l’ensemble des couples (x, y) tels que x est ´el´ement de A et y ´el´ement de B. Le produit cart´esien de A et B est not´e A × B. Cette analogie avec la notation de la multiplication est aussi employ´ee en Caml, d’o`u le symbole * dans les types. Les valeurs de types produit se notent comme en math´ematiques : on ´ecrit les deux ´el´ements du couple entre parenth`eses et s´epar´es par une virgule. Une petite diff´erence d’appellation cependant : en informatique on parle plus volontiers de paires que de couples. De plus, en Caml, les parenth`eses autour des paires ne sont pas toujours strictement n´ecessaires. # (1, 2);; - : int * int = 1, 2 Les paires sont aussi utilis´ees en tant qu’arguments ou r´esultats de fonctions. # let addition (x, y) = x + y;; addition : int * int -> int = # addition (1, 2);; - : int = 3 `A l’aide de paires, on ´ecrit des fonctions qui rendent plusieurs r´esultats. Par exemple, la fonction suivante calcule simultan´ement le quotient et le reste d’une division enti`ere : # let quotient_reste (x, y) = ((x / y), (x mod y));; quotient_reste : int * int -> int * int = # quotient_reste (5, 3);; - : int * int = 1, 2 Les notations pour les paires se g´en´eralisent aux triplets, aux quadruplets, et en fait aux n-uplets pour n’importe quel nombre d’´el´ements n. Par exemple, (1, 2, 3) est un triplet d’entiers et poss`ede le type int * int * int. 64 Fonctionnelles et polymorphisme 4.4 Curryfication `A proprement parler, une fonction prenant une paire comme argument ne poss`ede quand mˆeme qu’un seul argument et non pas deux. La fonction addition ci-dessus, qui prend un seul argument qui se trouve ˆetre une paire, est diff´erente de la fonction add suivante, qui prend deux arguments. # let add x y = x + y;; add : int -> int -> int = Du point de vue pratique, la diff´erence est minime, il est vrai. D’un point de vue technique, une fonction qui re¸coit ses arguments un par un (comme add) est dite curryfi´ee. En revanche, une fonction qui re¸coit tous ses arguments `a la fois sous la forme d’une paire ou plus g´en´eralement d’un n-uplet de valeurs est dite non curryfi´ee. Le n´eologisme « curryfier » n’est pas une allusion `a la cuisine indienne, mais un hommage au logicien Haskell Curry. Application partielle La diff´erence essentielle entre add et addition tient dans la mani`ere de les appliquer : il est l´egal d’appliquer la fonction add `a un seul argument, obtenant ainsi une fonction comme r´esultat, tandis que la fonction addition doit forc´ement recevoir ses deux entiers en mˆeme temps. Cette capacit´e des fonctions curryfi´ees de ne recevoir qu’un certain nombre de leurs arguments permet l’application partielle. Par exemple, en appliquant (partiellement) add `a l’entier 1, on obtient la fonction successeur. # let successeur = add 1;; successeur : int -> int = # successeur 3;; - : int = 4 Curryfication et type fl`eche Une fonction curryfi´ee est donc un cas particulier de fonctionnelle, puisqu’elle permet de cr´eer d’autres fonctions, en fixant certains de ses arguments. Cette propri´et´e est en fait inscrite dans le type d’une fonction curryfi´ee. Par exemple, le type de add est int -> int -> int. Or, le constructeur de type -> associe `a droite, ce qui signifie que le type de add n’est autre que int -> (int -> int). Cette ´ecriture explicitement parenth´es´ee indique clairement que add est une fonctionnelle : ´etant donn´e un entier, add retourne une autre fonction dont le type est justement (int -> int). Cela paraˆıt difficile `a comprendre au premier abord, mais c’est simplement une autre mani`ere de voir des phrases aussi simple que « ajouter 2 au r´esultat pr´ec´edent », qui signifie en fait : utiliser l’addition avec l’un des arguments fix´e `a 2 et appliquer cette fonction au r´esultat pr´ec´edent. En Caml, cela correspondrait `a ´evaluer : (add 2) (« r´esultat pr´ec´edent »);; Une autre approche f´econde est de consid´erer add comme une fonction g´en´erique, qui permet d’obtenir la famille de toutes les fonctions qui ajoutent une constante `a leur argument (et qui sont donc de type int -> int). Par exemple, la fonction add_3, qui ajoute 3 `a son argument, est d´efinie par : Une fonctionnelle de tri polymorphe 65 # let add_3 = add 3;; add_3 : int -> int = L’application partielle d’une fonction curryfi´ee pour fixer certains de ces arguments se justifie lorsque la fonction est tr`es g´en´erale. Dans ce cas, cette op´eration de sp´ecialisation permet de retrouver des fonctions int´eressantes en elles-mˆemes. Nous en verrons un exemple avec le tri, o`u fixer l’argument fonctionnel correspondant `a la comparaison permet de d´efinir le tri en ordre croissant ou le tri en ordre d´ecroissant. De cette ´etude des fonctions curryfi´ees, retenons que le constructeur de type → est associatif `a droite, ce qui signifie tout simplement que : t1 → t2 → t3 est ´equivalent `a t1 → (t2 → t3) 4.5 Une fonctionnelle de tri polymorphe Le polymorphisme n’est pas r´eserv´e au style fonctionnel. Pour le montrer, nous d´efinissons ici une proc´edure qui trie en place un tableau. La proc´edure ne calcule pas de valeurs, mais modifie l’ordre des ´el´ements dans le tableau. Nous g´en´eralisons ensuite cette proc´edure pour la rendre applicable `a tout type d’ordre. Trier des tableaux en ordre croissant L’algorithme de tri employ´e ici repose sur une id´ee tr`es simple : on cherche le minimum des ´el´ements du tableau et on le met `a la premi`ere place. Puis on cherche le minimum du reste du tableau et on le met `a la seconde place. Il suffit d’it´erer ce proc´ed´e jusqu’`a avoir parcouru compl`etement le tableau. Ce tri s’appelle le tri par s´election. Son principe s’illustre graphiquement par le sch´ema suivant, qui montre la ii`eme ´etape du tri par s´election, consistant `a d´eplacer t.(j) en t.(i), o`u j est l’indice tel que t.(j) est le plus petit des ´el´ements t.(i), t.(i + 1), . . . t 0 i − 1 i j t.(j) est le minimum `a partir de i vect_length(t)−1 d´efinitivement en place z }| { Pour traduire cet algorithme en Caml, nous avons besoin d’une proc´edure auxiliaire qui se charge d’´echanger deux ´el´ements dans un tableau. Il est bien connu que cette tˆache pr´esente une petite difficult´e : il faut garder une copie d’un des ´el´ements, puisqu’on perd cet ´el´ement quand on ´ecrit l’autre `a sa place. Cela m´erite bien une proc´edure ind´ependante de la fonction de tri. La proc´edure d’´echange est naturellement polymorphe : elle peut s’appliquer `a tout type de tableau puisqu’elle effectue une op´eration ind´ependante de la nature des ´el´ements du tableau. # let ´echange t i j = (* ´echange les ´el´ements i et j de t *) let ´el´ement_i = t.(i) in t.(i) <- t.(j); 66 Fonctionnelles et polymorphisme t.(j) <- ´el´ement_i;; ´echange : ’a vect -> int -> int -> unit = Il n’y a plus de difficult´e `a ´ecrire en Caml le tri par s´election : # let tri t = for i = 0 to vect_length t - 2 do let min = ref i in for j = i + 1 to vect_length t - 1 do if t.(j) <= t.(!min) then min := j done; ´echange t i !min done;; tri : ’a vect -> unit = G´en´eralisation `a tout type d’ordre Pour rendre cette proc´edure polymorphe encore plus g´en´erale, il suffit de remarquer que c’est l’emploi de la primitive <= pour comparer les ´el´ements du tableau qui impose le rangement par ordre croissant. Il suffit donc de passer la fonction de comparaison en argument pour trier selon toute sorte d’ordres. Lorsqu’on passe ainsi en argument une fonction utilis´ee dans le corps d’une d´efinition, on dit qu’on « abstrait » la fonction. Dans le cas du tri on abstrait donc la comparaison et la fonction tri prend alors un argument suppl´ementaire, ordre, une fonction `a deux arguments x et y renvoyant true si x est inf´erieur ou ´egal `a y et false sinon (ces fonctions sont appel´ees pr´edicats). # let tri ordre t = for i = 0 to vect_length t - 2 do let min = ref i in for j = i + 1 to vect_length t - 1 do if ordre t.(j) t.(!min) then min := j done; ´echange t i !min done;; tri : (’a -> ’a -> bool) -> ’a vect -> unit = Trions par ordre croissant ou d´ecroissant un tableau d’entiers, en changeant simplement l’ordre : # let t = [|3; 1; 2|] in tri (function x -> function y -> x <= y) t; t;; - : int vect = [|1; 2; 3|] # let t = [|3; 1; 2|] in tri (function x -> function y -> x >= y) t; t;; - : int vect = [|3; 2; 1|] Ce n’est pas plus difficile pour les tableaux de chaˆınes de caract`eres, en utilisant les fonctions pr´ed´efinies de comparaison ge_string (sup´erieur ou ´egal sur les chaˆınes) ou le_string (inf´erieur ou ´egal), qui comparent deux chaˆınes dans l’ordre lexicographique, c’est-`a-dire l’ordre du dictionnaire. # let t = [|"Salut "; "les "; "copains!"|] in tri (function x -> function y -> ge_string x y) t; t;; - : string vect = [|"les "; "copains!"; "Salut "|] La pleine fonctionnalit´e 67 # let t = [|"Salut "; "les "; "copains!"|] in tri (function x -> function y -> le_string x y) t; t;; - : string vect = [|"Salut "; "copains!"; "les "|] On constate sur cet exemple que les lettres majuscules pr´ec`edent les lettres minuscules dans l’ordre lexicographique utilis´e en Caml. Nous pouvons maintenant appliquer partiellement la proc´edure tri `a des ordres habituels, pour obtenir des proc´edures de tri sp´ecialis´ees : # let tri_croissant t = tri (function x -> function y -> x <= y) t;; tri_croissant : ’a vect -> unit = # let tri_d´ecroissant t = tri (function x -> function y -> x >= y) t;; tri_d´ecroissant : ’a vect -> unit = # let tri_du_dictionnaire = tri le_string;; tri_du_dictionnaire : string vect -> unit = # let t = [| "Bonjour"; "tout"; "le"; "monde" |] in tri_du_dictionnaire t; t;; - : string vect = [|"Bonjour"; "le"; "monde"; "tout"|] En conclusion, nous avons pu d´efinir une proc´edure de tri tr`es g´en´erale : grˆace au polymorphisme, cette proc´edure s’applique `a tout type de tableau ; grˆace `a la pleine fonctionnalit´e, elle s’applique `a tout type d’ordre. On retrouve les proc´edures de tri habituelles en sp´ecialisant certains param`etres de la proc´edure g´en´erale. 4.6 La pleine fonctionnalit´e Nous avons vu que les fonctions peuvent ˆetre pass´ees en arguments ou rendues en r´esultat, comme toutes les autres donn´ees. Plus ´etonnant encore, on les manipule comme des valeurs ordinaires `a l’int´erieur des structures de donn´ees. Nous ´etudions maintenant un exemple qui nous am`ene tr`es naturellement `a utiliser des tableaux de fonctions. Menu `a deux cas Notre but est d’´ecrire une fois pour toutes une proc´edure qui affiche un menu, lit le choix de l’utilisateur et lance l’option correspondante du menu. Pour simplifier, nous nous restreignons dans un premier temps aux menus qui offrent exactement deux possibilit ´es. La proc´edure prend donc quatre arguments : deux messages d’invite `a afficher et deux proc´edures correspondantes. Apr`es avoir affich´e le menu, elle lit l’option retenue par l’utilisateur `a l’aide de la fonction pr´ed´efinie read_int, qui lit un entier tap´e au clavier, puis appelle l’option correspondante, en testant la r´eponse de l’utilisateur. # let menu invite1 option1 invite2 option2 = print_string ("<0>: " ^ invite1); print_string " "; print_string ("<1>: " ^ invite2); print_newline (); print_string "Choisissez votre option: "; let r´eponse = read_int () in if r´eponse = 0 then option1 () else option2 ();; menu : string -> (unit -> ’a) -> string -> (unit -> ’a) -> ’a = 68 Fonctionnelles et polymorphisme Pour nos essais, nous d´efinissons deux petites proc´edures qui impriment simplement un message au terminal : # let au_revoir () = print_string "Au revoir"; print_newline ();; au_revoir : unit -> unit = # let continuer () = print_string "Continuons!"; print_newline ();; continuer : unit -> unit = Nous obtenons alors le dialogue suivant : # menu "Arr^eter" au_revoir "Continuer" continuer;; <0>: Arr^eter <1>: Continuer Choisissez votre option: 1 Continuons! - : unit = () Menu `a plusieurs cas Pour g´en´eraliser la proc´edure pr´ec´edente `a un nombre quelconque d’options, il suffit de lui passer deux tableaux en arguments : un tableau de chaˆınes de caract`eres pour les messages d’invite et un tableau de proc´edures pour les options. Il faut maintenant ´ecrire le menu avec une boucle for parcourant le tableau des messages, puis lire l’option choisie par l’utilisateur et s´electionner la proc´edure correspondante du tableau des options. # let menu invites options = for i = 0 to vect_length invites - 1 do print_string ("<" ^ (string_of_int i) ^ ">: " ^ invites.(i) ^ " ") done; print_newline (); print_string "Choisissez votre option: "; let r´eponse = read_int () in options.(r´eponse) ();; menu : string vect -> (unit -> ’a) vect -> ’a = La fonction pr´ed´efinie string_of_int renvoie la chaˆıne de caract`eres correspondant `a son argument entier. `A titre d´emonstratif, nous appelons la proc´edure avec une troisi`eme option qui consiste `a ne rien faire : la proc´edure associ´ee est simplement la fonction identit´e (sp´ecialis´ee au type unit) que nous fournissons comme une fonction anonyme. # menu [| "Arr^eter"; "Continuer"; "Ne rien faire" |] [| au_revoir; continuer; (function () -> ()) |];; <0>: Arr^eter <1>: Continuer <2>: Ne rien faire Choisissez votre option 2 - : unit = () Utiliser les types pour ´eviter les erreurs Fournir deux tableaux distincts pour les options et les messages d’invite est source d’erreurs, puisque le typage n’assure pas la correspondance entre l’invite et l’option. La correction est ais´ee : il suffit de n’utiliser qu’un seul tableau contenant des paires dont La pleine fonctionnalit´e 69 le premier ´el´ement est un message d’invite et le second l’option associ´ee. Cet exemple nous am`ene `a d´efinir les fonctions d’acc`es aux composantes d’une paire, traditionnellement nomm´ees fst (pour first, qui signifie « premier » en anglais) et snd (pour second, « second »). Bien que ces fonctions soient pr´ed´efinies en Caml, nous ´ecrivons leur code car il est ´el´egant. On op`ere tout simplement par filtrage de la paire argument : # let fst (x, y) = x;; fst : ’a * ’b -> ’a = # let snd (x, y) = y;; snd : ’a * ’b -> ’b = Une fois de plus, le polymorphisme nous autorise `a d´efinir ces deux fonctions pour tous les types de paires. La fonction menu est maintenant sans surprises. # let menu invites_options = for i = 0 to vect_length invites_options - 1 do print_string ("<" ^ (string_of_int i) ^ ">: "); print_string (fst (invites_options.(i)) ^ " ") done; print_newline (); print_string "Choisissez votre option: "; let r´eponse = read_int () in (snd (invites_options.(r´eponse))) ();; menu : (string * (unit -> ’a)) vect -> ’a = # menu [| ("Arr^eter", au_revoir); ("Continuer", continuer); ("Ne rien faire", (function () -> ())) |];; <0>: Arr^eter <1>: Continuer <2>: Ne rien faire Choisissez votre option: 0 Au revoir - : unit = () Un menu polymorphe tr`es g´en´eral R´efl´echissons encore un peu sur la proc´edure menu : la quintessence de cette proc´edure n’est pas d’appliquer directement les options, mais plutˆot de retourner un certain ´el´ement d’un tableau d’options, selon la r´eaction de l’utilisateur aux propositions affich´ees. Un pas de plus dans la g´en´eralisation consiste donc `a ne pas consid´erer que les options doivent forc´ement ˆetre des proc´edures. On se contente alors de retourner le deuxi`eme ´el´ement du couple correspondant au message d’invite choisi par l’utilisateur. # let menu invites_options = for i = 0 to vect_length invites_options - 1 do print_string ("<" ^ (string_of_int i) ^ ">: "); print_string (fst (invites_options.(i)) ^ " "); print_string " " done; print_newline (); print_string "Choisissez votre option"; let r´eponse = read_int () in snd (invites_options.(r´eponse));; 70 Fonctionnelles et polymorphisme menu : (string * ’a) vect -> ’a = Ainsi, la proc´edure menu retourne aussi bien des entiers que des fonctions. Voici par exemple un morceau de programme qui d´eterminerait le niveau de difficult´e `a prendre en compte dans un jeu. Ici la fonction menu retourne un entier. # let niveau_de_difficult´e = print_string "^Etes-vous"; print_newline (); menu [| ("D´ebutant ?", 1); ("Amateur ?", 2); ("Amateur confirm´e ?", 5); ("Expert ?", 10) |];; ^Etes-vous <0>: D´ebutant ? <1>: Amateur ? <2>: Amateur confirm´e ? <3>: Expert ? Choisissez votre option: 0 niveau_de_difficult´e : int = 1 Nous avons cependant toujours le loisir d’appeler menu avec des options fonctionnelles. # let option = menu [| ("Arr^eter", au_revoir); ("Continuer", continuer); ("Ne rien faire", (function () -> ())) |] in option ();; <0>: Arr^eter <1>: Continuer <2>: Ne rien faire Choisissez votre option: 0 Au revoir - : unit = () Il est bien entendu que la fonction menu reste na¨ıve : il lui faudrait tester la validit´e de la r´eponse de l’utilisateur et l’interroger `a nouveau en cas d’erreur. La validation de la r´eponse pourrait s’effectuer `a l’aide d’une fonction, argument suppl´ementaire de menu. On peut aussi envisager de lire des chaˆınes de caract`eres au lieu de nombres (par exemple "oui" ou "non"). Il n’en demeure pas moins que le polymorphisme et la pleine fonctionnalit´e nous permettent d’´ecrire une fonction tr`es g´en´erale dans laquelle les probl`emes de mise en page des menus, d’obtention d’une r´eponse et de validation de la r´eponse obtenue seront factoris´es une fois pour toutes. Vous en savez maintenant assez pour passer au chapitre suivant. Ce qui suit est ´etonnant mais technique. En particulier, nous verrons que le langage est assez puissant pour d´efinir un moyen automatique de passer de la version curryfi´ee `a la version non curryfi´ee d’une fonction. 4.7 Composition de fonctions En utilisant des fonctionnelles, on parvient `a programmer des notions math´ematiques qui paraissent a priori hors de port´ee d’une machine. Pour les lecteurs f´erus de math´ematiques, nous allons ´etudier un exemple surprenant : la composition des fonctions. Il est non seulement possible de d´efinir la composition de deux fonctions donn´ees en Caml, mais mˆeme d’´ecrire un programme qui impl´emente le fameux op´erateur « ◦ ». Rappelons que composer deux fonctions revient `a les appliquer successivement : la compos´ee des fonctions f et g, qu’on note f ◦ g en math´ematiques (prononcer « f rond g »), est la fonction h d´efinie par h(x) = f(g(x)). On fait souvent le sch´ema suivant : Composition de fonctions 71 A B C ×x × y = g (x) × z = f (y) = f (g (x)) g f f ◦ g Ainsi, l’op´erateur ◦ des math´ematiques est une fonctionnelle qui prend deux fonctions en arguments et renvoie une fonction : leur compos´ee. Il n’y a pas de difficult´es `a d´efinir l’op´erateur ◦ en Caml ; nous l’impl´ementons sous le nom de compose. # let compose f g = function x -> f (g x);; compose : (’a -> ’b) -> (’c -> ’a) -> ’c -> ’b = Le type de la fonction compose refl`ete fid`element les restrictions qu’on doit imposer `a deux fonctions pour pouvoir effectivement les composer. D’ailleurs, ces restrictions apparaissent dans notre sch´ema : il faut que l’ensemble de d´epart de la fonction f soit le mˆeme que l’ensemble d’arriv´ee de la fonction g. De plus, par d´efinition, la compos´ee f ◦ g de f et de g a pour ensemble de d´epart celui de g et pour ensemble d’arriv´ee celui de f. On le constate graphiquement, si l’on ne fait figurer dans le sch´ema de la composition que les ensembles et les fonctions qui interviennent (sch´ema ci-dessous `a gauche). Le contrˆoleur de type de Caml calcule un type o`u les noms A, B, C sont remplac´es respectivement par les param`etres ’c, ’a et ’b (sch´ema de droite). A B C g f f ◦ g ’c ’a ’b g f compose f g Le contrˆoleur de type a donc retrouv´e tout seul les contraintes math´ematiques et les v´erifiera automatiquement `a chaque composition de fonctions. Consid´erons l’exemple d’´ecole du successeur de la longueur d’une chaˆıne de caract`eres, d’abord sous forme graphique, puis en Caml. string int int ×c × y = string_length (c) × z = successeur (y) string_length successeur longueur_plus_un 72 Fonctionnelles et polymorphisme # let longueur_plus_un = compose successeur string_length;; longueur_plus_un : string -> int = # longueur_plus_un "OK";; - : int = 3 La composition de ces deux fonctions dans l’ordre inverse n’a pas de sens, ce que le contrˆoleur de types signale : # compose string_length successeur;; Entr´ee interactive: >compose string_length successeur;; > ^^^^^^^^^^ Cette expression est de type int -> int, mais est utilis´ee avec le type int -> string. La fonctionnelle de curryfication Nous allons d´efinir une fonctionnelle, curry, pour obtenir automatiquement la version curryfi´ee d’une fonction non curryfi´ee `a deux arguments. Notre fonctionnelle prendra donc en argument une fonction f dont l’argument est une paire (x, y) et rendra en r´esultat une fonction `a deux arguments (qui est donc de la forme function x -> function y -> . . . ) rendant le mˆeme r´esultat que f. On obtient donc let curry f = (function x -> function y -> «mˆeme r´esultat que f pour x et y »);; Puisque le «mˆeme r´esultat que f pour x et y » n’est autre que f (x, y), on a donc simplement : # let curry f = function x -> (function y -> f (x, y));; curry : (’a * ’b -> ’c) -> ’a -> ’b -> ’c = Le type de la fonction curry est plus clair si l’on r´etablit les parenth`eses omises par l’imprimeur de types de Caml (une fois n’est pas coutume). En effet (’a * ’b -> ’c) -> ’a -> ’b -> ’c se lit aussi (’a * ’b -> ’c) -> (’a -> ’b -> ’c). La fonctionnelle de d´ecurryfication La fonctionnelle r´eciproque, uncurry, n’est pas plus complexe. Elle prend en argument une fonction curryfi´ee g et rend en r´esultat une fonction ayant un seul argument qui est une paire. Ce r´esultat est donc une valeur de la forme function (x, y) -> . . . let uncurry g = (function (x, y) -> «mˆeme r´esultat que g pour x et y »);; Or, puisque g est curryfi´ee `a deux arguments, le «mˆeme r´esultat que g pour x et y » est g x y. # let uncurry g = function (x, y) -> g x y;; uncurry : (’a -> ’b -> ’c) -> ’a * ’b -> ’c = De mˆeme que pour curry, le type de uncurry se lit plus facilement en r´etablissant les parenth`eses autour de ’a * ’b -> ’c. Composition de fonctions 73 Leurs compos´ees Intuitivement, il est clair que les fonctionnelles curry et uncurry sont r´eciproques l’une de l’autre : `a partir d’une fonction `a « un argument de type paire » curry renvoie une fonction « `a deux arguments », tandis que uncurry fait l’inverse. D’ailleurs : # let id_curry f = (compose curry uncurry) f;; id_curry : (’a -> ’b -> ’c) -> ’a -> ’b -> ’c = # let id_uncurry f = (compose uncurry curry) f;; id_uncurry : (’a * ’b -> ’c) -> ’a * ’b -> ’c = Une fonctionnelle inutile Consid´erez la fonctionnelle suivante qui applique une fonction f donn´ee `a un argument x donn´e : # let applique f x = f x;; applique : (’a -> ’b) -> ’a -> ’b = Cette fonctionnelle ne sert `a rien ! D’abord, si vous examinez soigneusement son type, vous vous rendrez compte que c’est une sp´ecialisation du type de l’identit´e : (’a -> ’b) -> ’a -> ’b est une abr´eviation pour (’a -> ’b) -> (’a -> ’b), c’est-`a-dire ’a -> ’a avec ’a sp´ecialis´e en (’a -> ’b). Donc, applique pourrait bien ne rien faire, comme la fonction identit´e. On le d´emontre facilement, en utilisant toujours la r`egle  avec l’argument x cette fois : let applique f x = f x;; ´equivaut `a let applique f = f;; ce qui d´emontre que applique est s´emantiquement ´equivalente `a l’identit´e, sp´ecialis´ee aux fonctions. En effet, applique impose `a son argument f d’ˆetre fonctionnel puisqu’elle l’applique `a l’argument x. Cela nous fournit un exemple o`u l’application de la r`egle  change le type de la fonction qu’on d´efinit, le rendant plus g´en´eral : # let applique f = f;; applique : ’a -> ’a = ... id id id id id id id = id id id id id id = id id id id id = id id id id = id id id =id id =id=functionx->x 5 Listes La tˆete et la queue, sans les oreilles . . . armi les structures de donn´ees pr´ed´efinies en Caml, les listes ont un statut privil´egi´e : elles sont d’emploi fr´equent, car simples `a manipuler et tr`es g´en´erales. Si vous savez que les listes peuvent `a peu pr`es tout coder, mais qu’il ne faut pas pour autant les mettre `a toutes les sauces, en bref, si vous connaissez bien it_list et que justement vous ´evitez de l’employer trop souvent, alors lisez directement le chapitre suivant. Nous verrons, dans celui-ci, une autre repr´esentation des polynˆomes par des listes de paires d’entiers et nous animerons le jeu des tours de Hanoi en montrant l’´evolution de l’´etat du jeu. Au passage, nous approfondirons le filtrage (appel explicite au filtrage, synonymes dans les filtres, filtrage multiple) et nous verrons des exemples de fonctionnelles sur les listes. 5.1 Pr´esentation Syntaxe Les listes sont des suites homog`enes de valeurs, entour´ees de crochets [ et ]. Comme pour les tableaux, les ´el´ements des listes sont s´epar´es par un point-virgule « ; ». # [1; 2; 3];; - : int list = [1; 2; 3] Au contraire des tableaux, on n’acc`ede pas directement `a un ´el´ement de liste : il faut parcourir s´equentiellement la liste pour atteindre l’´el´ement recherch´e. En revanche, les listes peuvent grossir dynamiquement alors que les tableaux ont une taille d´etermin´ee, fix´ee lors de leur construction. Cependant on ajoute toujours les ´el´ements au d´ebut d’une liste et non `a la fin. C’est contraire `a la pratique courante : quand on tient `a jour une liste de courses, on ajoute g´en´eralement la prochaine course `a faire `a la fin de sa liste . . . Toutes les listes sont construites avec les deux constructeurs de listes, « [] » (qu’on prononce « nil », d’apr`es l’anglais nil qui signifie n´eant et qui provient du latin nihil qui veut dire rien) et « :: » (qu’on prononce « conse », par abr´eviation de « constructeur de 76 Listes liste »). [] est la liste vide et :: est l’op´erateur infixe qui ajoute un ´el´ement en tˆete d’une liste. Tout comme le tableau vide, la liste vide est polymorphe. # [];; - : ’a list = [] # 0 :: [1; 2; 3];; - : int list = [0; 1; 2; 3] # 3 :: [];; - : int list = [3] # 1 :: 2 :: 3 :: [];; - : int list = [1; 2; 3] Repr´esentation graphique des listes Pour expliquer certaines fonctions qui manipulent des listes, nous repr´esentons graphiquement les calculs qu’elles effectuent. Pour cela nous dessinons les listes comme des peignes dont les dents contiennent les ´el´ements de la liste. Par exemple, la liste trois_entiers d´efinie par : # let trois_entiers = [1; 2; 3];; trois_entiers : int list = [1; 2; 3] est repr´esent´ee par le sch´ema ci-contre. Cette repr´esentation en peigne a l’avantage de mettre en ´evidence la construction de la liste `a partir de ses ´el´ements, de la liste vide, et d’applications successives du constructeur « :: ». En effet, la liste trois_entiers vaut 1 :: 2 :: 3 :: [] et vous aurez sans doute remarqu´e que la notation [e1; e2; . . . ; en] est une abr´eviation pour e1 :: e2 :: . . . :: en :: []. Lorsque nous expliquerons des fonctions sur les listes, la liste argument la plus g´en´erale sera not´ee l et ses ´el´ements seront not´es e1, e2, . . . , en. Cette liste la plus g´en´erale sera donc d´ecrite graphiquement par le peigne en marge. :: :: :: 1 2 3 [] trois_entiers :: :: . . . :: e1 e2 en [] l Filtrage des listes Le filtrage est ´etendu aux listes, si bien qu’on teste si une liste est vide avec la fonction suivante : # let nulle = function | [] -> true | _ -> false;; nulle : ’a list -> bool = Ce texte Caml se lit comme suit : si l’argument de la fonction nulle est la liste vide, alors retourner true ([] -> true) ; dans tous les autres cas (_), retourner false. Par exemple : # (nulle []), (nulle [1]);; - : bool * bool = true, false Programmation assist´ee par filtrage 77 De plus, il est possible de nommer la tˆete ou le reste d’une liste argument avec des filtres utilisant « :: ». # let t^ete = function | t :: r -> t | _ -> failwith "t^ete";; t^ete : ’a list -> ’a = # t^ete [1; 2; 3; 4];; - : int = 1 La clause t :: r -> t signifie : si la liste argument n’est pas vide et qu’on appelle sa tˆete t et son reste r (t :: r), alors retourner t. La clause | _ -> signifie comme d’habitude « dans tous les autres cas ». La fonction failwith est une fonction pr´ed´efinie qui signale une erreur : en anglais, failwith signifie « ´echouer avec ». Vous devinez donc que failwith "t^ete" signale une erreur dans la fonction t^ete. # t^ete [];; Exception non rattrap´ee: Failure "t^ete" Le syst`eme nous signale ainsi une exception non rattrap´ee («Uncaught exception »), c’est-`a-dire un ´echec, avec pour message la chaˆıne t^ete (Failure "t^ete"). Nous reviendrons sur ce m´ecanisme d’´echec et sur la notion d’exception au chapitre 7. Comme d’habitude, il n’est pas obligatoire de nommer une partie de la valeur argument qu’on n’utilise pas. Par exemple, il n’est pas n´ecessaire de nommer la tˆete d’une liste pour retourner son reste (on dit aussi la queue de la liste) : # let reste = function | _ :: r -> r | _ -> failwith "reste";; reste : ’a list -> ’a list = # reste [1; 2; 3; 4];; - : int list = [2; 3; 4] 5.2 Programmation assist´ee par filtrage Il est facile de d´efinir une fonction qui additionne tous les ´el´ements d’une liste d’entiers : si la liste est vide, alors le r´esultat est 0 ; sinon, on ajoute le premier ´el´ement de la liste `a la somme des ´el´ements du reste de la liste. # let rec somme = function | [] -> 0 | x :: l -> x + somme l;; somme : int list -> int = On calcule le produit des ´el´ements d’une liste de fa¸con similaire. # let rec produit = function | [] -> 1 | x :: l -> x * produit l;; produit : int list -> int = Ces deux derni`eres fonctions sont des exemples repr´esentatifs : les fonctions sur les listes sont le plus souvent r´ecursives et op`erent un filtrage sur la liste argument. 78 Listes G´en´eralement, la fonction s’arrˆete quand elle rencontre une liste vide et s’appelle r´ecursivement quand la liste n’est pas vide. Le squelette g´en´eral d’une telle fonction est donc : let rec f = function | [] -> « valeur de base » | x :: l -> ... f(l) ...;; Cela vous explique pourquoi les fonctions nulle, t^ete et reste ne sont gu`ere employ´ees en Caml : `a quoi bon tester explicitement if nulle(l) then . . . else . . . , quand le filtrage permet de le faire bien plus ´el´egamment. En effet, en n’employant pas le filtrage, le squelette g´en´eral d’une fonction r´ecursive sur les listes s’´ecrirait bien plus lourdement : let rec f l = if nulle l then « valeur de base » else let x = t^ete l and l’ = reste l in ... f(l’) ...;; Le mod`ele g´en´eral de fonction r´ecursive d´efinie par filtrage sur les listes est `a rapprocher de celui qu’on a vu sur les entiers : let rec f = function | 0 -> « solution simple » | n -> ... f (n - 1) ...;; Sch´ematiquement, le squelette le plus g´en´eral de fonctions r´ecursives Caml est sugg´er´e par le pseudo-programme suivant : let rec f = function | « cas simple » -> « solution simple » | « autre cas simple » -> « autre solution simple » ... | « cas g´en´eral » -> ... f (« cas plus simple ») ...;; Il est donc tr`es fr´equent que le filtrage dirige l’´ecriture des fonctions. Par exemple, si l’on d´efinit une fonction sur les listes, on ´ecrira (presque) sans r´efl´echir : let rec f = function | [] -> | x :: l -> `A titre d’entraˆınement avant d’attaquer les fonctionnelles d’usage g´en´eral sur les listes, nous allons mettre en pratique cette « programmation assist´ee par filtrage » dans un cas simple : le tri des listes. 5.3 Tri par insertion Nous impl´ementons le tri par insertion, qui est un algorithme naturellement r´ecursif. On suppose qu’une sous-partie du tableau `a trier est d´ej`a tri´ee et on y ins`ere `a la bonne place le prochain ´el´ement de la partie non tri´ee du tableau. Nous en donnons une version fonctionnelle sur les listes. Tri par insertion 79 Tri sur listes L’id´ee est qu’il est facile de ranger un ´el´ement `a sa place dans une liste d’´el´ements d´ej`a tri´ee. Nous supposons donc avoir d´ej`a ´ecrit une fonction ins`ere qui ins`ere un ´el´ement `a la bonne place dans une liste tri´ee. Nous ´ecrivons maintenant la fonction de tri. Cette fonction travaille sur des listes ; elle doit donc envisager les deux cas possibles de listes : let tri_par_insertion = function | [] -> ... | x :: reste -> ... ;; Le cas de la liste vide est simple : une liste vide est ´evidemment tri´ee ; on renvoie donc la liste vide. let tri_par_insertion = function | [] -> [] | x :: reste -> ... ;; Dans l’autre cas, on va commencer par trier le reste de la liste. C’est d´ej`a possible, bien que nous n’ayons pas encore ´ecrit notre fonction de tri : il suffit d’appeler r´ecursivement la fonction tri_par_insertion que nous sommes justement en train d’´ecrire . . . let rec tri_par_insertion = function | [] -> [] | x :: reste -> ... tri_par_insertion reste;; Il nous suffit maintenant de mettre l’´el´ement x `a la bonne place dans le reste maintenant tri´e de la liste. C’est facile : on se contente d’appeler la fonction ins`ere. Nous obtenons : let rec tri_par_insertion = function | [] -> [] | x :: reste -> ins`ere x (tri_par_insertion reste);; La fonction de tri est termin´ee. Il nous reste `a ´ecrire la fonction ins`ere. Par le mˆeme raisonnement que ci-dessus on commence par en ´ecrire le squelette : let ins`ere ´el´ement = function | [] -> ... | x :: reste -> ...;; Le cas de la liste vide est encore une fois simple : il suffit de retourner une liste r´eduite `a l’´el´ement qu’on souhaite ins´erer. let ins`ere ´el´ement = function | [] -> [´el´ement] | x :: reste -> ...;; Dans l’autre cas, la liste o`u l’on veut ins´erer ´el´ement commence par x. Si ´el´ement est plus petit que x alors c’est le plus petit de tous les ´el´ements de la liste x :: reste, puisque celle-ci est tri´ee par hypoth`ese. On place donc ´el´ement au d´ebut de la liste x :: reste. let ins`ere ´el´ement = function | [] -> [´el´ement] | x :: reste -> if ´el´ement <= x then ´el´ement :: x :: reste else ...;; Dans le cas contraire, c’est x le plus petit ´el´ement de la liste r´esultat ; ce r´esultat sera donc x :: ... Il nous reste `a ins´erer ´el´ement dans la liste reste. Un petit appel r´ecursif ins`ere ´el´ement reste et le tour est jou´e : 80 Listes # let rec ins`ere ´el´ement = function | [] -> [´el´ement] | x :: reste -> if ´el´ement <= x then ´el´ement :: x :: reste else x :: (ins`ere ´el´ement reste);; ins`ere : ’a -> ’a list -> ’a list = Il nous reste `a d´efinir effectivement la fonction de tri et `a l’essayer : # let rec tri_par_insertion = function | [] -> [] | x :: reste -> ins`ere x (tri_par_insertion reste);; tri_par_insertion : ’a list -> ’a list = # tri_par_insertion [3; 2; 1];; - : int list = [1; 2; 3] Synonymes dans les filtres Pour am´eliorer la lisibilit´e du code de la fonction ins`ere, nous introduisons une facilit´e de nommage suppl´ementaire dans les filtres. Il arrive que l’on veuille examiner la forme d’une valeur tout en nommant cette valeur. Consid´erez la fonction qui rend la valeur absolue d’un monˆome, repr´esent´e comme une paire d’entier (coefficient, degr´e) : # let abs_mon^ome = function (a, degr´e) -> if a < 0 then (-a, degr´e) else (a, degr´e);; abs_mon^ome : int * ’a -> int * ’a = Ce code est parfaitement correct, mais dans le cas o`u le coefficient est positif on aimerait rendre directement le monˆome re¸cu en argument. Le code serait plus clair, puisqu’il n’y aurait pas besoin d’une petite gymnastique mentale pour se rendre compte que l’expression (a, degr´e) correspond exactement au filtre de la clause. Autrement dit, nous voudrions nommer mon^ome le filtre (a, degr´e) et rendre mon^ome quand a est positif. Dans ce cas, on introduit le nom choisi avec le mot-cl´e as (qui se prononce « ase » et signifie « en tant que » en anglais). Synonymes dans les filtres ::= filtre as nom Nous obtenons : # let abs_mon^ome = function (a, degr´e) as mon^ome -> if a < 0 then (-a, degr´e) else mon^ome;; abs_mon^ome : int * ’a -> int * ’a = Maintenant le nommage indique `a l’´evidence qu’aucune transformation n’est faite sur le monˆome, alors que l’expression (a, degr´e), bien qu’´equivalente, cache un peu qu’elle n’est autre que l’argument de la fonction. Pour la fonction ins`ere, l’usage d’un filtre synonyme pour nommer la liste argument clarifie ´egalement un peu le code : # let rec ins`ere ´el´ement = function | [] -> [´el´ement] | x :: reste as l -> if ´el´ement <= x then ´el´ement :: l else x :: (ins`ere ´el´ement reste);; ins`ere : ’a -> ’a list -> ’a list = Fonctionnelles simples sur les listes 81 G´en´eralisation du tri `a tout type d’ordre Pour g´en´eraliser la fonction de tri `a toute sorte d’ordres, il suffit de passer la fonction de comparaison en argument, comme on l’a vu au chapitre 4. Les fonctions ins`ere et tri_par_insertion prennent alors un argument suppl´ementaire, ordre, qu’on utilise pour comparer les ´el´ements, `a la place de la comparaison <=. # let rec ins`ere ordre ´el´ement = function | [] -> [´el´ement] | x :: reste as l -> if ordre ´el´ement x then ´el´ement :: l else x :: (ins`ere ordre ´el´ement reste);; ins`ere : (’a -> ’a -> bool) -> ’a -> ’a list -> ’a list = # let rec tri_par_insertion ordre = function | [] -> [] | x :: reste -> ins`ere ordre x (tri_par_insertion ordre reste);; tri_par_insertion : (’a -> ’a -> bool) -> ’a list -> ’a list = La mˆeme fonction nous permet maintenant de trier indiff´eremment des listes de chaˆınes ou de nombres, `a l’endroit ou `a l’envers : # tri_par_insertion (function x -> function y -> x <= y) [3; 1; 2];; - : int list = [1; 2; 3] # tri_par_insertion (function x -> function y -> x >= y) [3; 1; 2];; - : int list = [3; 2; 1] # tri_par_insertion (function x -> function y -> ge_string x y) ["Salut "; "les "; "copains!"];; - : string list = ["les "; "copains!"; "Salut "] # tri_par_insertion (function x -> function y -> le_string x y) ["Salut "; "les "; "copains!"];; - : string list = ["Salut "; "copains!"; "les "] Remarque de complexit´e : on d´emontre que ce tri est quadratique (O(n2)) en moyenne (sur un jeu de donn´ees tir´ees au hasard). Dans le pire des cas, c’est-`a-dire quand le jeu de donn´ees n´ecessite le plus d’op´erations (ce qui correspond pour ce tri `a une liste tri´ee en ordre inverse), le tri par insertion est ´egalement quadratique. En revanche, il est lin´eaire pour une liste d´ej`a tri´ee. 5.4 Fonctionnelles simples sur les listes Les listes, comme toutes les structures de donn´ees polymorphes, se prˆetent naturellement `a la d´efinition de fonctionnelles r´eutilisables dans de nombreuses situations. Nous pr´esentons dans cette section quelques-unes de ces fonctionnelles, parmi les plus simples ; nous en verrons d’autres, plus complexes, `a la section 5.9. Faire une action sur les ´el´ements d’une liste ´E tant donn´ees une fonction f et une liste l, la fonctionnelle do_list applique f tour `a tour `a tous les ´el´ements de l. Cela correspond donc `a effectuer des appels `a f en s´equence sur les ´el´ements de l. Autrement dit, ´evaluer do_list f [e1; e2; . . . ; en] signifie ex´ecuter la s´equence begin f e1; f e2; . . . ; f en; () end. Par exemple : 82 Listes # do_list print_int [1; 2; 3];; 123- : unit = () # do_list (function i -> print_int i; print_char ‘ ‘) [1; 2; 3];; 1 2 3 - : unit = () ´E crivons do_list : # let rec do_list f = function | [] -> () | x :: l -> f x; do_list f l;; do_list : (’a -> ’b) -> ’a list -> unit = Le raisonnement est le suivant : si la liste argument est vide, il n’y a rien `a faire. Sinon, la liste argument est de la forme x :: l, car elle n’est pas vide. En ce cas, il faut ´evaluer en s´equence f(x), puis l’action de f sur le reste de la liste, qu’on obtient par un appel r´ecursif `a do_list. Remarquons que le type de la fonction do_list indique clairement que la fonction f doit accepter en argument les ´el´ements de la liste, puisque f a pour type ’a -> ’b et que la liste a pour type ’a list. En revanche, les r´esultats des appels `a f sont ignor´es : f peut rendre un r´esultat de n’importe quel type. Appliquer `a tous les ´el´ements d’une liste ´E tant donn´ees une fonction f et une liste l, la fonctionnelle map retourne la liste des r´esultats obtenus en appliquant f `a chaque ´el´ement de l. Par exemple, map successeur [1;2;3] retourne la liste [2;3;4], tandis que map carr´e [1;2;3] retourne [1;4;9]. L’expression map f [e1; e2; . . . ; en] retourne donc la liste [f e1; f e2; . . . ; f en]. Graphiquement, la fonction map « distribue » f sur les ´el´ements de la liste argument : :: :: . . . :: e1 e2 en [] l :: :: . . . :: f e1 f e2 f en [] map f l map f Le nom map provient du langage Lisp et signifie « application multiple ». La fonction s’´ecrit tr`es simplement : si la liste argument est vide le r´esultat est la liste vide. Sinon, la liste argument est de la forme x :: l et il suffit de mettre en tˆete du r´esultat l’application de f `a x, le reste du r´esultat ´etant fourni par un appel r´ecursif `a map. # let rec map f = function | [] -> [] | x :: l -> f x :: map f l;; map : (’a -> ’b) -> ’a list -> ’b list = Cette fois-ci le type d’arriv´ee de la fonction f n’est plus ignor´e : il doit ˆetre identique au type des ´el´ements de la liste des r´esultats. # map succ [1; 2; 3];; - : int list = [2; 3; 4] Les polynˆomes creux 83 # map string_length ["Bonjour"; "tout"; "le "; "monde!"];; - : int list = [7; 4; 3; 6] Nous allons utiliser nos nouvelles connaissances sur les listes pour calculer des polynˆomes qui nous ´etaient pr´ec´edemment inaccessibles. 5.5 Les polynˆomes creux Une repr´esentation affin´ee Notre premier programme de manipulation des polynˆomes (section 3.3) repr´esentait les polynˆomes par des tableaux d’entiers. Cette repr´esentation est tr`es dispendieuse quand les polynˆomes ont peu de monˆomes dont les degr´es sont tr`es diff´erents : il serait insupportable de repr´esenter le polynˆome 1+x10000 par un tableau `a 10001 cases dont 9999 comporteraient des z´eros ! Les polynˆomes ayant cette propri´et´e d’avoir une forte proportion de coefficients nuls sont appel´es polynˆomes creux. Par opposition nous avions appel´e polynˆomes pleins les polynˆomes de la section 3.3, repr´esent´es par le tableau des coefficients de leurs monˆomes. Une repr´esentation plus compacte des polynˆomes creux utilise tout naturellement la liste ordonn´ee des coefficients non nuls, avec leurs degr´es respectifs. Il s’agit donc de listes de paires d’entiers. Dans cette repr´esentation, le polynˆome 1 + 2X3 serait : # let p = [(1, 0); (2, 3)];; p : (int * int) list = [1, 0; 2, 3] Nous allons red´efinir les op´erations sur les polynˆomes, en travaillant sur des polynˆomes creux. Impression des polynˆomes creux L’impression est tr`es simple : nous utilisons exactement la mˆeme fonction d’impression des monˆomes que dans le cas des polynˆomes pleins, mais nous l’it´erons sur la liste des coefficients `a l’aide de la fonctionnelle do_list. # let imprime_polyn^ome_creux p = do_list (function (a, degr´e) -> imprime_mon^ome a degr´e) p;; imprime_polyn^ome_creux : (int * int) list -> unit = # imprime_polyn^ome_creux p;; 1 + 2x^3- : unit = () (Les amateurs de casse-tˆete compareront la fonction imprime_polyn^ome_creux avec la fonction do_list (uncurry imprime_mon^ome) ou mˆeme compose do_list uncurry imprime_mon^ome.) L’addition est un peu plus compliqu´ee. En effet, elle op`ere un filtrage en parall`ele sur les deux polynˆomes arguments. Ce filtrage est complexe et utilise des traits nouveaux pour nous. Nous les d´etaillons avant d’analyser le code de l’addition des polynˆomes creux. 84 Listes 5.6 Filtrage explicite La construction match ... with Pour filtrer ses deux arguments en parall`ele, la fonction d’addition des polynˆomes utilise un appel explicite au filtrage, m´ecanisme introduit par le mot-cl´e match : Appel explicite au filtrage ::= match expression with filtrage Cette construction a la s´emantique d’une conditionnelle par cas, ou encore d’un if g´en´eralis´e travaillant par filtrage. Red´efinissons la fonction nulle avec un appel explicite au filtrage : on demande explicitement d’examiner la forme de la liste l argument de nulle et l’on renvoie dans chaque cas l’expression ad´equate. # let nulle l = match l with | [] -> true | _ -> false;; nulle : ’a list -> bool = On lit ce filtrage comme la phrase : si la liste l est vide, renvoyer true ; dans tous les autres cas, renvoyer false. En utilisant le filtrage explicite, nous pouvons tr`es facilement ´ecrire la fonction qui concat`ene deux listes : si la premi`ere liste est vide, le r´esultat est la deuxi`eme ; sinon, il faut mettre la tˆete de la premi`ere liste devant la concat´enation du reste de cette liste avec la deuxi`eme liste. Ce qui, parce que bien con¸cu, s’´enonce clairement ainsi : # let rec concat`ene l1 l2 = match l1 with | [] -> l2 | x :: reste -> x :: concat`ene reste l2;; concat`ene : ’a list -> ’a list -> ’a list = # concat`ene [1; 2; 3] [4; 5; 6];; - : int list = [1; 2; 3; 4; 5; 6] Filtrage simultan´e de deux valeurs Pour envisager tous les cas concernant la forme de deux listes, il suffit de filtrer explicitement la paire des deux listes. Ainsi, la fonction suivante d´etermine si deux listes ont mˆeme longueur : # let rec m^eme_longueur l1 l2 = match (l1, l2) with | ([], []) -> true | (_ :: reste1, _ :: reste2) -> m^eme_longueur reste1 reste2 | (_, _) -> false;; m^eme_longueur : ’a list -> ’b list -> bool = # m^eme_longueur [1] [2];; - : bool = true # m^eme_longueur [1] [1; 2];; - : bool = false Op´erations sur les polynˆomes creux 85 Le filtre (_ :: reste1, _ :: reste2) signifie : si la premi`ere liste l1 n’est pas vide et donc de la forme _ :: reste1 et si simultan´ement la seconde liste l2 est non vide et de la forme _ :: reste2, alors . . . En particulier, les soulign´es « _ » mettent bien en ´evidence qu’on ne s’int´eresse pas `a la tˆete des listes, mais qu’on a nomm´e leurs restes respectifs reste1 et reste2. Remarquez que le dernier cas du filtrage correspond `a des listes d’in´egales longueurs : l’une est ´epuis´ee mais l’autre ne l’est pas. En effet, si les deux ´etaient ´epuis´ees le premier cas s’appliquerait, tandis que si aucune n’´etait ´epuis´ee, c’est le second qui s’appliquerait. Autrement dit : le cas (_, _) regroupe en une seule clause les deux cas ([], _ :: _) -> false et (_ :: _, []) -> false. Remarquons ´egalement que le filtrage simultan´e ne n´ecessite pas les parenth`eses des couples ; la construction permet de filtrer des expressions s´epar´ees par des virgules avec leurs filtres respectifs, eux aussi s´epar´es par des virgules : match l1, l2 with | [], [] -> true | _ :: reste1, _ :: reste2 -> ... 5.7 Op´erations sur les polynˆomes creux Addition des polynˆomes creux L’addition des polynˆomes creux va analyser r´ecursivement ses deux arguments pour en construire la somme. Comme nous l’avons vu dans le cas des polynˆomes pleins, il arrive que certains termes d’un des deux polynˆomes arguments n’aient pas de correspondants dans l’autre, parce que les polynˆomes n’ont pas le mˆeme degr´e. Dans le cas des polynˆomes creux, l’une des listes de monˆomes sera ´epuis´ee avant l’autre. Si nous atteignons ainsi la fin de l’un des polynˆomes, l’autre constitue le r´esultat cherch´e : par exemple, si l’on ajoute un polynˆome P0 r´eduit `a une constante `a un autre polynˆome P, il faut ajouter les deux monˆomes de degr´e 0 de P et P0, mais le r´esultat comprend aussi la liste des monˆomes restants de P. En effet, les coefficients manquants du polynˆome de plus bas degr´e correspondent `a des z´eros implicites. Dans le cas g´en´eral, nous ajoutons les termes de mˆeme degr´e ou recopions dans le r´esultat final les termes qui n’ont pas d’analogue dans l’autre polynˆome. # let rec ajoute_polyn^omes_creux p1 p2 = match p1, p2 with | _, [] -> p1 | [], _ -> p2 | (a1, degr´e1 as m1) :: reste1, (a2, degr´e2 as m2) :: reste2 -> if degr´e1 = degr´e2 then ((a1 + a2), degr´e1) :: ajoute_polyn^omes_creux reste1 reste2 else if degr´e1 < degr´e2 then m1 :: ajoute_polyn^omes_creux reste1 p2 else m2 :: ajoute_polyn^omes_creux p1 reste2;; ajoute_polyn^omes_creux : (int * ’a) list -> (int * ’a) list -> (int * ’a) list = Le filtre (a1,degr´e1 as m1) :: reste1, (a2,degr´e2 as m2) :: reste2 est complexe et n´ecessite une explication. Il est clairement constitu´e de deux filtres analogues 86 Listes s´epar´es par une virgule, l’un pour filtrer p1 et l’autre pour filtrer p2. Examinons celui qui concerne p1. Le filtre (a1,degr´e1 as m1) :: reste1 signifie que : • p1 est une liste non vide dont la tˆete est filtr´ee par (a1, degr´e1 as m1) et le reste est nomm´e reste1, • la tˆete de p1 est donc un couple dont les composantes sont nomm´ees a1 et degr´e1, • le couple lui-mˆeme, (a1, degr´e1), est nomm´e m1 grˆace au filtre synonyme as m1. Admirons au passage la puissance et l’´el´egance du m´ecanisme de filtrage. Remarquez ´egalement que les filtres sont essay´es dans l’ordre de pr´esentation dans le filtrage. Par exemple, la valeur ([], []) sera filtr´ee par le premier filtre, bien qu’elle soit aussi filtrable par le second. `A titre d’exemple, nous calculons la somme des polynˆomes X2 + 3X4 et 3 + 2X2 + 5X10. # imprime_polyn^ome_creux (ajoute_polyn^omes_creux [(1,2); (3,4)] [(3,0); (2,2); (5,10)]);; 3 + 3x^2 + 3x^4 + 5x^10- : unit = () Multiplication des polynˆomes creux La multiplication op`ere ´egalement par filtrage simultan´e de ses deux arguments. Dans le cas o`u l’un des polynˆomes est ´epuis´e, il n’y a plus de multiplication `a faire. En effet, les monˆomes manquants ont implicitement des coefficients nuls, donc les multiplications produiront toujours des coefficients nuls. En ce cas, le r´esultat est donc la liste vide. Sinon, on applique simplement la r`egle habituelle de distributivit´e de la multiplication par rapport `a l’addition. Voyons : soit m1 le premier monˆome de P1 et reste1 les autres monˆomes de P1. On a P1 = m1+reste1, donc P1×P2 = m1×P2+ reste1×P2. Si l’on appelle notre fonction multiplie_polyn^omes_creux, alors reste1×P2 correspond `a l’appel r´ecursif multiplie_polyn^omes_creux reste1 p2. Quant `a l’expression m1 ×P2, c’est un cas plus simple o`u l’on multiplie un polynˆome par un monˆome. Nous le traiterons par la fonction auxiliaire multiplie_par_mon^ome_creux. L’expression m1×P2+reste1×P2 s’´ecrit donc : ajoute_polyn^omes_creux (multiplie_par_mon^ome_creux m1 p2) (multiplie_polyn^omes_creux reste1 p2) Il reste `a d´efinir multiplie_par_mon^ome_creux. Si m1 est le monˆome et P le polynˆome, il suffit de multiplier chaque monˆome de P par le monˆome m1, ce qui se fait simplement en multipliant les coefficients et en ajoutant les degr´es. En r´esum´e : # let multiplie_par_mon^ome_creux (a1, degr´e1) p = map (function (a, degr´e) -> (a * a1, degr´e1 + degr´e)) p;; multiplie_par_mon^ome_creux : int * int -> (int * int) list -> (int * int) list = # let rec multiplie_polyn^omes_creux p1 p2 = match p1, p2 with | (_, []) -> [] | ([], _) -> [] | (m1 :: reste1, _) -> Op´erations sur les polynˆomes creux 87 ajoute_polyn^omes_creux (multiplie_par_mon^ome_creux m1 p2) (multiplie_polyn^omes_creux reste1 p2);; multiplie_polyn^omes_creux : (int * int) list -> (int * int) list -> (int * int) list = Nous calculons (1 + X10000)2 `a titre d’exemple : # let p = [(1, 0); (1, 10000)] in imprime_polyn^ome_creux (multiplie_polyn^omes_creux p p);; 1 + 2x^10000 + x^20000- : unit = () En premi`ere lecture, vous en savez largement assez pour passer d`es maintenant au chapitre suivant. Polymorphisme et r`egle  Nous devons signaler ici une petite difficult´e qui apparaˆıt lorsqu’on utilise la r`egle  pour simplifier une d´efinition, lorsque le r´esultat doit ˆetre polymorphe. Supposons que nous d´efinissions le tri par ordre croissant ainsi : # let tri_croissant l = tri_par_insertion (function x -> function y -> x <= y) l;; tri_croissant : ’a list -> ’a list = On peut esp´erer simplifier cette d´efinition `a l’aide de la r`egle , en supprimant l’argument l : # let tri_croissant = tri_par_insertion (function x -> function y -> x <= y);; tri_croissant : ’_a list -> ’_a list = On constate alors que la fonction tri_croissant n’a plus le mˆeme type, et qu’il appara ˆıt dans ce type d’´etranges param`etres de type ’_a.Au contraire des param`etres ’a qui signifient pour tout type a, et d´enotent donc des types polymorphes, les param`etres ’_a signifient pour un certain type a qui sera d´etermin´e par les utilisations ult´erieures de la fonction. La fonction tri_croissant est donc monomorphe : # tri_croissant [3; 2; 1];; - : int list = [1; 2; 3] # tri_croissant;; - : int list -> int list = # tri_croissant ["Bonjour"];; Entr´ee interactive: >tri_croissant ["Bonjour"];; > ^^^^^^^^^^^ Cette expression est de type string list, mais est utilis´ee avec le type int list. Le type inconnu ’_a est devenu le type int et la fonction tri_croissant est dor´enavant de type int -> int. Ce ph´enom`ene est dˆu `a la coexistence en Caml du polymorphisme et des structures mutables. Il est expliqu´e en d´etails `a la fin de ce livre, page 363, lorsque nous aurons vu les m´ecanismes qui permettent de le comprendre. Retenons pour l’instant que seule les fonctions (et les constantes) sont susceptibles d’ˆetre polymorphes, les d´efinitions de 88 Listes fonctions obtenues par application partielle d’une fonction plus g´en´erale sont monomorphes. Nous avons d´ej`a constat´e (page 73) que l’application de la r`egle  peut modifier le type d’une fonction, le rendant plus g´en´eral ; ici, c’est l’inverse : on passe d’un type polymorphe `a un type monomorphe moins g´en´eral. 5.8 Animation des tours de Hanoi En guise d’exercice sur les listes, nous d´efinissons un ensemble de fonctions qui manipulent des listes de chaˆınes pour animer visuellement le jeu des tours de Hanoi. C’est un vrai programme qui utilise des r´ef´erences et la r´ecursivit´e. Cela reste cependant un exercice : nous n’avons aucun souci d’efficacit´e ici. Ce genre de programme d’impression ´elabor´ee (le formatage) est plus du ressort de la modification physique de chaˆınes de caract`eres que de la manipulation de listes. # let blancs n = make_string n ‘ ‘;; blancs : int -> string = # let disque taille = let moiti´e_droite = make_string taille ‘>‘ and moiti´e_gauche = make_string taille ‘<‘ in moiti´e_gauche ^ "|" ^ moiti´e_droite;; disque : int -> string = Ces deux fonctions construisent respectivement la repr´esentation sous forme d’une chaˆıne d’une ligne vide de longueur n et d’un disque de largeur taille. Par exemple, le disque de largeur 3 est repr´esent´e par la chaˆıne "<<<|>>>" : la chaˆıne "|" repr´esente un morceau de tige et "<<<" et ">>>" les parties gauche et droite du disque. La fonction suivante construit un disque, pos´e sur une tige, c’est-`a-dire entour´e d’un certain nombre d’espaces, de fa¸con `a ce que le disque occupe la mˆeme largeur que la tige sur laquelle il est pos´e : # let disque_num´ero n taille_grand_disque = let partie_blanche = blancs (taille_grand_disque + 1 - n) in partie_blanche ^ (disque n) ^ partie_blanche;; disque_num´ero : int -> int -> string = La derni`ere fonction dessine la base d’une tige sous la forme " ___|___ ". # let base_de_tige taille_grand_disque = let moiti´e = make_string taille_grand_disque ‘_‘ in " " ^ moiti´e ^ "|" ^ moiti´e ^ " ";; base_de_tige : int -> string = Un disque est repr´esent´e par un simple num´ero : sa largeur. Une tige est repr´esent´ee par un couple (entier, liste d’entiers). La premi`ere composante est le nombre de cases libres en haut de la tige, la seconde la liste des disques pos´es sur la tige. La fonction tige suivante construit la liste des chaˆınes de caract`eres repr´esentant les disques pos´es sur une tige. # let rec tige taille_grand_disque = function | (0, []) -> [] | (0, t^ete :: reste) -> disque_num´ero t^ete taille_grand_disque :: Animation des tours de Hanoi 89 tige taille_grand_disque (0, reste) | (d´ecalage, liste) -> disque_num´ero 0 taille_grand_disque :: tige taille_grand_disque (d´ecalage-1, liste);; tige : int -> int * int list -> string list = Par exemple, voici ce qu’on obtient pour la tige (1, [2; 3; 5]), c’est-`a-dire une case libre, puis trois disques de largeur 2, 3 et 5 : # let imprime ligne = print_string ligne; print_newline ();; imprime : string -> unit = # do_list imprime (tige 5 (1, [2; 3; 5]));; | <<|>> <<<|>>> <<<<<|>>>>> - : unit = () Par la suite, nous allons avoir besoin d’afficher non pas une tige, mais trois tiges cˆote `a cˆote. La fonction recolle cr´ee la liste des lignes `a afficher `a partir des trois listes de lignes correspondant aux trois tiges. # let rec recolle l1 l2 l3 = match l1, l2, l3 with | [], [], [] -> [] | t1 :: r1, t2 :: r2, t3 :: r3 -> (t1 ^ t2 ^ t3) :: recolle r1 r2 r3 | _ -> failwith "recolle";; recolle : string list -> string list -> string list -> string list = L’affichage d’une configuration consiste simplement `a imprimer les lignes qui repr´esentent les disques, puis `a imprimer les trois bases des tiges. # let imprime_jeu nombre_de_disques d´epart milieu arriv´ee = let dessin = recolle (tige nombre_de_disques d´epart) (tige nombre_de_disques milieu) (tige nombre_de_disques arriv´ee) in do_list imprime dessin; let b = base_de_tige nombre_de_disques in imprime (b ^ b ^ b);; imprime_jeu : int -> int * int list -> int * int list -> int * int list -> unit = Nous impl´ementons maintenant les d´eplacements de disques d’une tige `a l’autre. Voyons tout d’abord la fonction qui ajoute un disque au sommet d’une tige. Par exemple, dans un jeu `a quatre disques, ajouter le troisi`eme disque `a une tige qui ne comprend que le disque num´ero 4 correspond `a l’´evaluation de ajoute_disque 3 (2, [4]), qui retourne (1, [3; 4]). # let ajoute_disque disque (d´ecalage, disques as tige) = (d´ecalage - 1, disque :: disques);; ajoute_disque : ’a -> int * ’a list -> int * ’a list = On d´efinit la fonction sommet pour consulter le disque qui se trouve au sommet d’une tige et la fonction enl`eve_sommet pour ˆoter le sommet d’une tige (plus exactement, pour renvoyer la tige priv´ee de son sommet). 90 Listes # let sommet = function | (d´ecalage, sommet :: reste) -> sommet | (d´ecalage, []) -> failwith "sommet: tige vide";; sommet : ’a * ’b list -> ’b = # let enl`eve_sommet = function | (d´ecalage, sommet :: reste) -> (d´ecalage + 1, reste) | (d´ecalage, []) -> failwith "enl`eve_sommet: tige vide";; enl`eve_sommet : int * ’a list -> int * ’a list = Nous pouvons maintenant simuler un mouvement en d´epla¸cant un disque d’une tige `a l’autre. La proc´edure prend en argument des r´ef´erences sur les tiges concern´ees et les modifie physiquement au passage. # let d´eplace (nom_d´epart, tige_d´epart) (nom_arriv´ee, tige_arriv´ee) = imprime("Je d´eplace un disque de " ^ nom_d´epart ^ " `a " ^ nom_arriv´ee); let disque_d´eplac´e = sommet !tige_d´epart in tige_d´epart := enl`eve_sommet !tige_d´epart; tige_arriv´ee := ajoute_disque disque_d´eplac´e !tige_arriv´ee;; d´eplace : string * (int * ’a list) ref -> string * (int * ’a list) ref -> unit = La mod´elisation de l’´etat initial du jeu n´ecessite la d´efinition d’une tige vide et d’une tige pleine, en fonction du nombre de disques utilis´es. # let tige_vide nombre_de_disques = (nombre_de_disques, []);; tige_vide : ’a -> ’a * ’b list = # let tige_pleine nombre_de_disques = let rec liste_des_disques i = if i <= nombre_de_disques then i :: liste_des_disques (i + 1) else [] in (0, liste_des_disques 1);; tige_pleine : int -> int * int list = Nous d´efinissons maintenant une proc´edure de r´esolution compl`ete incluant les fonctions d’impression. Nous maintenons l’´etat des trois tiges `a l’aide de trois r´ef´erences, gauche, milieu et droite. Remarquez que la proc´edure r´ecursive locale hanoi prend ces trois r´ef´erences en argument et qu’elles sont modifi´ees physiquement par la proc´edure d´eplace. # let jeu nombre_de_disques = let gauche = ref (tige_pleine nombre_de_disques) and milieu = ref (tige_vide nombre_de_disques) and droite = ref (tige_vide nombre_de_disques) in let rec hanoi hauteur d´epart interm´ediaire destination = if hauteur > 0 then begin hanoi (hauteur - 1) d´epart destination interm´ediaire; d´eplace d´epart destination; imprime_jeu nombre_de_disques !gauche !milieu !droite; hanoi (hauteur - 1) interm´ediaire d´epart destination end in Fonctionnelles complexes sur les listes 91 # jeu 3;; J’appelle les tiges A, B et C. Position de d´epart: Je d´eplace un disque de A `a C <|> | | | | | <<|>> | | | <|> | <<<|>>> | | | <<|>> <<<|>>> ___|___ ___|___ ___|___ ___|___ ___|___ ___|___ Je d´eplace un disque de A `a C Je d´eplace un disque de B `a A | | | | | | <<|>> | | | | | <<<|>>> | <|> <|> <<|>> <<<|>>> ___|___ ___|___ ___|___ ___|___ ___|___ ___|___ Je d´eplace un disque de A `a B Je d´eplace un disque de B `a C | | | | | | | | | | | <<|>> <<<|>>> <<|>> <|> <|> | <<<|>>> ___|___ ___|___ ___|___ ___|___ ___|___ ___|___ Je d´eplace un disque de C `a B Je d´eplace un disque de A `a C | | | | | <|> | <|> | | | <<|>> <<<|>>> <<|>> | | | <<<|>>> ___|___ ___|___ ___|___ ___|___ ___|___ ___|___ - : unit = () Figure 5.1: R´esolution des tours de Hanoi `a 3 disques imprime "J’appelle les tiges A, B et C."; imprime "Position de d´epart:"; imprime_jeu nombre_de_disques !gauche !milieu !droite; hanoi nombre_de_disques ("A", gauche) ("B", milieu) ("C", droite);; jeu : int -> unit = La figure 5.1 montre le d´eroulement de jeu 3. 5.9 Fonctionnelles complexes sur les listes Nous allons maintenant passer en revue un certain nombre de fonctionnelles classiques sur les listes. Elles sont d’un emploi plus rare que celles que nous avons d´ej`a vues. Nous les utiliserons dans les exemples les plus difficiles. Vous pourrez alors vous reporter aux explications qui suivent. Notion d’abstraction de sch´emas de programmes Vous vous souvenez sans doute que pour g´en´eraliser les fonctions de tri du chapitre 4 et du pr´esent chapitre, il nous avait suffi de passer la comparaison en param`etre. Nous avions dit que nous avions « abstrait » l’ordre. Les fonctionnelles que nous allons d´ecrire sont ´egalement bas´ees sur la notion d’abstraction. Cependant, dans ce cas il ne s’agit pas d’abstraire une fonction qui intervient dans le programme pour le g´en´eraliser, mais 92 Listes d’abstraire un sch´ema de programme, c’est-`a-dire une m´ethode de calcul commune `a plusieurs algorithmes. Pour d´egager cette m´ethode de calcul, nous allons montrer plusieurs fonctions qui l’utilisent. Nous rappelons d’abord le code des fonctions somme et produit, d´efinies ci-dessus ; puis nous ´ecrivons la fonction implose, qui renvoie la concat´enation de toutes les chaˆınes d’une liste et enfin la fonction concat`ene_listes qui concat`ene toutes les listes d’une liste de listes (ce dernier exemple utilise la fonction pr´ed´efinie @ qui concat`ene deux listes). # let rec somme = function | [] -> 0 | x :: l -> x + somme l;; somme : int list -> int = # let rec produit = function | [] -> 1 | x :: l -> x * produit l;; produit : int list -> int = # let rec implose = function | [] -> "" | x :: l -> x ^ implose l;; implose : string list -> string = # implose ["Bonjour"; "tout"; "le "; "monde!"];; - : string = "Bonjourtoutle monde!" # let rec concat`ene_listes = function | [] -> [] | x :: l -> x @ concat`ene_listes l;; concat`ene_listes : ’a list list -> ’a list = # concat`ene_listes [[1]; [2; 3]; [4; 5; 6]];; - : int list = [1; 2; 3; 4; 5; 6] On constate que toutes ces fonctions utilisent le mˆeme sch´ema r´ecursif : en cas de liste vide, on rend un certain ´el´ement de base ; en cas de liste non vide, on appelle une fonction `a deux arguments, avec pour premier argument la tˆete de la liste et pour second argument un appel r´ecursif sur la fin de la liste. Pour somme l’´el´ement de base est 0 et l’op´eration est +, pour produit c’est 1 et *, pour implose c’est "" et ^, enfin pour concat`ene_listes c’est [] et @. Pour obtenir notre fonctionnelle g´en´erale qui impl´emente ce sch´ema de programme, il nous suffit donc d’abstraire la fonction et l’´el´ement de base. Une petite remarque syntaxique au pr´ealable : toutes les op´erations utilis´ees ici sont infixes. Pour ´ecrire la fonctionnelle, nous utilisons un argument fonctionnel normal (donc pr´efixe). Il faut donc relire le code des exemples avec des op´erations pr´efixes : par exemple pour somme, l’op´eration pr´efixe correspondant `a + est la fonction pr´ed´efinie add_int, c’est-`a-dire function x -> function y -> x + y. Notre fonctionnelle s’´ecrit maintenant tr`es facilement : # let rec it´erateur_sur_listes f b = function | [] -> b | x :: l -> f x (it´erateur_sur_listes f b l);; it´erateur_sur_listes : (’a -> ’b -> ’b) -> ’b -> ’a list -> ’b = Pour d´efinir somme en utilisant l’it´erateur, on ´ecrira simplement : # let somme l = it´erateur_sur_listes add_int 0 l;; somme : int list -> int = Fonctionnelles complexes sur les listes 93 # somme [1; 2; 3];; - : int = 6 D’autres programmes ressortissent du mˆeme sch´ema. Consid´erons par exemple la fonction qui calcule la longueur d’une liste, qui est pr´ed´efinie en Caml sous le nom de list_length. Elle s’´ecrit sans difficult´e : # let rec list_length = function | [] -> 0 | x :: l -> 1 + list_length l;; list_length : ’a list -> int = C’est encore le mˆeme sch´ema : l’´el´ement de base est 0, l’op´eration est l’addition de 1. On peut donc ´ecrire # let list_length l = it´erateur_sur_listes (function x -> function y -> 1 + y) 0 l;; list_length : ’a list -> int = # list_length [0; 3; 5; 7];; - : int = 4 On ´ecrit de mˆeme la concat´enation des listes, `a l’aide d’une fonction auxiliaire devant qui recopie une liste devant une autre. # let rec devant l2 = function | [] -> l2 | x :: l -> x :: devant l2 l;; devant : ’a list -> ’a list -> ’a list = # devant [4; 5; 6] [1; 2; 3];; - : int list = [1; 2; 3; 4; 5; 6] # let concat`ene l1 l2 = devant l2 l1;; concat`ene : ’a list -> ’a list -> ’a list = La fonction devant suit ´egalement le mˆeme sch´ema, avec ´el´ement de base l2 et op´eration « :: ». # let devant l2 = it´erateur_sur_listes (function x -> function y -> x :: y) l2;; devant : ’a list -> ’a list -> ’a list = # devant [4; 5; 6] [1; 2; 3];; - : int list = [1; 2; 3; 4; 5; 6] Notion d’accumulation r´ecursive Un autre sch´ema r´ecursif qui revient souvent est l’accumulation. Par exemple, pour ´ecrire la fonction somme, il est naturel d’ajouter les ´el´ements de la liste `a un accumulateur, qu’on rendra en r´esultat quand on arrivera `a la fin de la liste. Nous sommes donc amen´es `a ´ecrire la fonction somme_accu suivante, qui accumule les ´el´ements d’une liste dans son accumulateur argument accu : # let rec somme_accu accu = function | [] -> accu | x :: l -> somme_accu (x + accu) l;; somme_accu : int -> int list -> int = 94 Listes Il est clair qu’on obtient la somme des ´el´ements d’une liste en appelant somme_accu avec un accumulateur valant initialement 0. # let somme l = somme_accu 0 l;; somme : int list -> int = # somme [1; 2; 3];; - : int = 6 La fonctionnelle g´en´erale correspondant au code de somme_accu est simplement : # let rec accumulateur_sur_listes f accu = function | [] -> accu | x :: l -> accumulateur_sur_listes f (f x accu) l;; accumulateur_sur_listes : (’a -> ’b -> ’b) -> ’b -> ’a list -> ’b = La fonction somme_accu s’obtient maintenant en utilisant accumulateur_sur_listes avec l’addition : # let somme_accu l = accumulateur_sur_listes (function x -> function accu -> x + accu) l;; somme_accu : int -> int list -> int = # let somme l = somme_accu 0 l;; somme : int list -> int = # somme [1; 2; 3];; - : int = 6 Les fonctionnelles pr´ed´efinies en Caml sont analogues `a celles que nous venons de d´egager, avec cependant des diff´erences minimes. La fonctionnelle it´erateur_sur_listes correspond `a list_it et accumulateur_sur_listes est ´equivalente `a it_list. Nous d´ecrivons maintenant ces fonctionnelles pr´ed´efinies en leur donnant une interpr´etation graphique. Accumuler avec les ´el´ements d’une liste ´E tant donn´es trois arguments f, b et l, la fonctionnelle it_list effectue de multiples compositions de la fonction `a deux arguments f, en utilisant les ´el´ements de la liste l comme seconds arguments de f. La valeur de base b est utilis´ee pour le premier argument du premier appel `a f, puis le r´esultat de chaque appel `a f est pass´e en premier argument de l’appel suivant. it_list est caract´eris´ee par : it_list f b [e1; e2; . . . ; en] = (f (. . . (f (f b e1) e2) . . .) en). Le code de it_list est : # let rec it_list f b = function | [] -> b | x :: l -> it_list f (f b x) l;; it_list : (’a -> ’b -> ’a) -> ’a -> ’b list -> ’a = Intuitivement, si l’on peut dire, it_list fait de la « r´e´ecriture de liste » : elle remplace tous les ; de la liste par f (consid´er´ee comme un op´erateur binaire infixe) et ajoute la valeur initiale b au premier appel `a f. Graphiquement, l’effet calculatoire de it_list se repr´esente par la transformation : Fonctionnelles complexes sur les listes 95 :: :: . . . :: e1 e2 en [] l f f . . . f en en−1 b e1 it_list f l b it_list Par exemple, supposons que f soit l’addition +. Nous obtenons it_list f b [e1; e2; . . . ; en] = b + e1 + e2 + · · · + en. Maintenant nous pouvons ´ecrire la fonction somme en utilisant it_list : # let ajoute x y = x + y;; ajoute : int -> int -> int = # let somme l = it_list ajoute 0 l;; somme : int list -> int = # somme [1; 2; 3];; - : int = 6 Allons encore un peu plus loin : il est inutile de d´efinir la fonction ajoute qui est ´equivalente `a l’op´erateur infixe +. En effet, en Caml on fait r´ef´erence `a la forme pr´efixe d’un op´erateur infixe (comme +) en le faisant simplement pr´ec´eder du mot-cl´e prefix : # (prefix +);; - : int -> int -> int = # (prefix +) 1 2;; - : int = 3 Cette facilit´e conduit `a des d´efinitions de somme et produit en une ligne (apr`es une ´etape de -contraction) : # let somme = it_list (prefix +) 0 and produit = it_list (prefix *) 1;; somme : int list -> int = produit : int list -> int = Graphiquement, cela s’exprime par les deux r´e´ecritures suivantes : :: :: . . . :: e1 e2 en [] l + + . . . + en en−1 0 e1 it_list (prefix +) 0 l somme :: :: . . . :: e1 e2 en [] l * * . . . * en en−1 1 e1 it_list (prefix *) 1 l produit 96 Listes Est-ce plus clair que notre premi`ere version de somme d´efinie par filtrage sur les listes ? Pas pour les auteurs de ce livre en tout cas : nous pensons que cette ´ecriture semblera souvent obscure `a d’autres programmeurs, y compris vous-mˆeme trois mois apr`es avoir ´ecrit ce code ! Si vous voulez ˆetre lisible, ´evitez ce style de « programmation sans variables », particuli`erement si vous ˆetes capable d’´ecrire du code qui utilise encore plus de fonctionnelles que celui de somme : ce code peut devenir trop « ´el´egant » pour ˆetre maintenu . . . Accumuler encore ´E tant donn´es trois arguments f, b et l, la fonctionnelle list_it effectue de multiples compositions de la fonction `a deux arguments f, en utilisant les ´el´ements de la liste l comme premiers arguments de f ; l’´el´ement de base b est utilis´e pour le second argument du premier appel `a f, puis le r´esultat de chaque appel `a f est pass´e en second argument de l’appel suivant. list_it est caract´eris´ee par : list_it f [e1; e2; . . . ; en] b = f e1 (f e2( . . . (f en b) . . . )). En termes encore plus savants et en utilisant l’op´erateur math´ematique de composition des fonctions ◦, on ´ecrirait : list_it f [e1; e2; . . . ; en] b = ((f e1) ◦ (f e2) ◦ · · · ◦ (f en)) (b) # let rec list_it f l b = match l with | [] -> b | x :: reste -> f x (list_it f reste b);; list_it : (’a -> ’b -> ’b) -> ’a list -> ’b -> ’b = Intuitivement, list_it fait aussi de la « r´e´ecriture de liste » : elle remplace toutes les occurrences de « :: » par f et le « [] » final par la valeur initiale b. :: :: . . . :: e1 e2 en [] l f f . . . f e1 e2 en b list_it f l b list_it Par exemple, la fonction qui copie une liste est facile `a d´efinir : elle doit remplacer tous les ::de la liste par d’autres :: et le [] final par []. La fonction f qui fait ce travail de ne rien modifier et donc de remplacer ::par :: , est simplement la fonction function x -> function l -> x :: l. Nous l’appelons cons et la d´efinissons ainsi : # let cons x l = x :: l;; cons : ’a -> ’a list -> ’a list = Avec list_it, nous ´ecrivons facilement la fonction de copie : Fonctionnelles complexes sur les listes 97 :: :: . . . :: e1 e2 en [] l cons cons . . . cons e1 e2 en [] list_it cons l [] list_it # let copie_liste l = list_it cons l [];; copie_liste : ’a list -> ’a list = # copie_liste [1;2;3];; - : int list = [1; 2; 3] Cette fonction n’est pas vraiment utile, sauf pour copier une liste devant une autre. En effet, si nous voulons maintenant concat´ener deux listes l1 et l2 (mettre les deux listes bout `a bout), il suffit de mettre l2 `a la fin de l1, donc de remplacer le [] final de l1 par toute la liste l2. :: :: . . . :: e1 e2 en [] l1 :: :: . . . :: e1 e2 en l2 concat`ene l1 l2 Il suffit donc d’appeler list_it sur l1 avec la fonction cons et l’´el´ement final l2. # let concat`ene l1 l2 = list_it cons l1 l2;; concat`ene : ’a list -> ’a list -> ’a list = # concat`ene [1; 2; 3] [4; 5; 6];; - : int list = [1; 2; 3; 4; 5; 6] :: :: . . . :: e1 e2 en [] l1 :: :: . . . :: e1 e2 en l2 list_it cons l1 l2 concat`ene D´efinitions r´ecursives locales Nous revenons sur le code de la fonction map pour faire la remarque suivante : ´etant donn´ee une fonction f, map se contente de boucler sur une liste, en appliquant f. C’est pourquoi il nous suffit de d´efinir une fonction r´ecursive `a l’int´erieur de map, qui saura appliquer f sur les ´el´ements d’une liste quelconque : # let map f = let rec map_fonction_f = function | [] -> [] | x :: l -> f x :: map_fonction_f l in (function liste -> map_fonction_f liste);; map : (’a -> ’b) -> ’a list -> ’b list = 98 Listes Ainsi, map n’est plus r´ecursive, mais comporte une d´efinition locale de fonction r´ecursive. On simplifie encore ce code en utilisant la r`egle , qui stipule que (function liste -> map_fonction_f liste) est ´equivalent `a la forme plus simple map_fonction_f. On obtient alors le code suivant : # let map f = let rec map_fonction_f = function | [] -> [] | x :: l -> f x :: map_fonction_f l in map_fonction_f;; map : (’a -> ’b) -> ’a list -> ’b list = Cette vision de map correspond `a celle d’une fonctionnelle qui, appliqu´ee `a une fonction f, retourne la fonction qui it`ere f sur une liste. Avec cette vision nous pourrions ´ecrire : # let successeur x = x + 1;; successeur : int -> int = # let liste_des_successeurs = map successeur;; liste_des_successeurs : int list -> int list = # liste_des_successeurs [0; 1; 2];; - : int list = [1; 2; 3] Ce style de programmation n’est pas `a encourager : la d´efinition na¨ıve de map nous paraˆıt plus claire. D’autre part, la d´efinition de map avec une fonction locale ne se justifie pas non plus par un gain majeur en efficacit´e : suivant les compilateurs, elle sera un peu plus ou un peu moins efficace que la d´efinition na¨ıve. En revanche, dans le cas o`u une fonction r´ecursive prend beaucoup de param`etres en argument et o`u ces param`etres sont inchang´es dans les appels r´ecursifs, on peut admettre de d´efinir localement une fonction r´ecursive qui s’appelle avec les seuls param`etres modifi´es. En ce cas, on remplacerait la d´efinition d’une fonction f `a plusieurs param`etres x1, x2, . . . , xn, dont les appels r´ecursifs ne font varier que xn, par une d´efinition comportant une fonction locale ayant xn pour seul param`etre. Ainsi let rec f x1 x2 x3 ... xn = ... f x1 x2 x3...(xn + 1) ... f x1 x2 x3...(xn - 1) ...;; deviendrait let f x1 x2 x3 ... = let rec f_locale xn = ... f_locale (xn + 1) ... f_locale (xn - 1) ... in f_locale;; Ce style ne se justifie que pour des raisons de concision ; il ne doit pas ˆetre ´erig´e en syst`eme. 5.10 Efficacit´e des fonctions sur les listes : ´etude de cas Dans cette section, nous analysons la complexit´e de deux fonctions, implose et rev. L’´etude de complexit´e nous am`ene `a ´ecrire des versions plus efficaces, en style imp´eratif pour implose et en style purement fonctionnel pour rev. L’am´elioration obtenue est bas´ee dans les deux cas sur l’emploi d’un accumulateur qui maintient les r´esultats Efficacit´e des fonctions sur les listes : ´etude de cas 99 interm´ediaires de la fonction. L’optimisation ne d´epend donc pas obligatoirement du style de programmation utilis´e, puisqu’une mˆeme id´ee sert dans les deux styles et pour des programmes diff´erents. La fonction implose, version fonctionnelle La fonction implose concat`ene toutes les chaˆınes d’une liste de chaˆınes. Nous en avons d´ej`a ´ecrit la version la plus na¨ıve dans la section 5.9. Raisonnons graphiquement : pour obtenir la concat´enation de toutes les chaˆınes d’une liste il faut « r´e´ecrire » la liste en rempla¸cant les « :: » par des « ^ » et le symbole [] final par la chaˆıne vide. Alg´ebriquement, si l’on note lc la liste argument, [c1; c2; . . . ; cn], on a implose [c1; c2; . . . ; cn] = c1 ^ c2^ . . . ^ cn ^ "" = list_it (prefix ^) lc "" :: :: . . . :: c1 c2 cn [] lc ^ ^ . . . ^ c1 c2 cn "" list_it (prefix ^) lc "" implose De la mˆeme fa¸con, mais en utilisant it_list au lieu de list_it : implose [c1; c2; . . . ; cn] = "" ^ c1^ . . . ^ cn = it_list (prefix ^) "" lc On obtient donc : # let implose lc = list_it (prefix ^) lc "";; implose : string list -> string = # let implose2 lc = it_list (prefix ^) "" lc;; implose2 : string list -> string = implose2 se simplifie par la r`egle , pour donner un code extrˆemement similaire `a celui de somme (op´eration binaire prefix ^ au lieu de prefix + et ´el´ement neutre "" au lieu de 0) : # let implose2 = it_list (prefix ^) "";; implose2 : string list -> string = Cette ´ecriture est extrˆemement compacte ; on peut mˆeme la consid´erer comme ´el´egante. Cependant l’emploi des it´erateurs, en produisant un code compact, a tendance `a cacher la complexit´e des algorithmes. Nous allons voir que notre fonction implose a une complexit ´e ´elev´ee (quadratique en le nombre de chaˆınes concat´en´ees), `a cause de la cr´eation de nombreuses chaˆınes interm´ediaires. 100 Listes L’op´erateur de concat´enation de chaˆınes Pour calculer la complexit´e de la fonction implose, il nous faut r´efl´echir sur le fonctionnement de l’op´erateur ^ de concat´enation de chaˆınes. ´Etant donn´ees deux chaˆınes de caract`eres c1 et c2 en arguments, ^ alloue une nouvelle chaˆıne de caract`eres pour y loger la concat´enation des deux chaˆınes, puis y recopie c1 et c2 correctement d´ecal´ees. c1 r r r r r r r r r r r r r r r c2 b b b b b b b b b b r r r r r r r r r r r r r r r b b b b b b b b b b c1 ^ c2 A AAU   Nous ´ecrivons l’op´erateur ^ sous la forme d’une fonction pr´efixe concat : # let concat c1 c2 = let r´esultat = create_string (string_length c1 + string_length c2) in blit_string c1 0 r´esultat 0 (string_length c1); blit_string c2 0 r´esultat (string_length c1) (string_length c2); r´esultat;; concat : string -> string -> string = On rappelle que l’appel de la proc´edure blit_string source indice_source destination indice_destination nombre_de_caract`eres_`a_transf´erer transf`ere nombre_de_caract`eres_`a_transf´erer dans la chaˆıne destination `a partir de l’indice indice_destination. Ces caract`eres sont ceux de la chaˆıne source `a partir de l’indice indice_source. source nombre_de_caract`eres_`a_transf´erer  - r r r r r r r r r 6 indice_source J J JJ^ destination r r r r r r r r r 6 indice_destination Pour calculer la complexit´e de la fonction implose, nous consid´ererons que le coˆut d’une concat´enation est ind´ependant de la longueur des deux chaˆınes concat´en´ees, ou encore que toutes les chaˆınes sont d’´egale longueur et mˆeme de longueur 1 pour simplifier encore. Nous supposons que la liste argument lc comprend n chaˆınes c1, c2, . . . cn. `A chaque chaˆıne ci de la liste de chaˆınes argument, on recopie le pr´ec´edent r´esultat dans une nouvelle chaˆıne f, puis on recopie la chaˆıne ci dans f. On alloue donc autant de chaˆınes que de r´esultats interm´ediaires, soit n. De plus, c1 est recopi´ee une fois, c2 deux fois, et cn est recopi´ee n fois. Ce qui donne donc 1 + 2 + 3 + · · · + n caract`eres copi´es. Or, un calcul facile (il suffit d’ajouter les termes de la somme en les groupant astucieusement, premier avec dernier, deuxi`eme avec avant-dernier, etc.) montre que 1 + 2 + 3 + · · · + n = n × (n + 1) 2 Efficacit´e des fonctions sur les listes : ´etude de cas 101 Quand n est grand, (n × (n + 1))/2 est proche de n2/2. Notre fonction a donc une complexit´e de l’ordre de n2 : elle est quadratique. Nous avons d´ej`a vu que c’est une complexit´e importante. Nous allons tenter de l’am´eliorer. La fonction implose, version imp´erative L’id´ee, tr`es simple, est de calculer d’abord la longueur de la chaˆıne n´ecessaire au r´esultat final, de l’allouer, puis d’y copier toutes les chaˆınes de la liste. # let implose cha^ınes = let longueur = ref 0 in do_list (function ch -> longueur := string_length ch + !longueur) cha^ınes; let r´esultat = create_string !longueur and position = ref 0 in do_list (function ch -> blit_string ch 0 r´esultat !position (string_length ch); position := !position + string_length ch) cha^ınes; r´esultat;; implose : string list -> string = La complexit´e de cette version est alors d’une seule allocation de chaˆıne et de n copies de caract`eres : cette version est donc lin´eaire. La diff´erence d’efficacit´e est ´enorme : pour 104 chaˆınes de caract`eres, la premi`ere version n´ecessite 108/2 copies, soit 5000 fois plus que l’algorithme lin´eaire. En d’autres termes, si la version lin´eaire demande 1 seconde pour r´ealiser la concat´enation des 104 chaˆınes, alors la version quadratique n´ecessite plus de 1 heure de calcul ! Nous avons ainsi optimis´e la fonction implose en passant dans le monde imp´eratif. Cette d´emarche n’est pas obligatoire ; l’optimisation consiste aussi `a remplacer un algorithme du monde fonctionnel par un autre plus efficace mais sans quitter le monde fonctionnel. C’est ce que nous allons voir maintenant. Retournement d’une liste La fonction rev est un exemple embl´ematique de fonction simple qui a pourtant un comportement catastrophique si l’on n’a pas le souci de r´efl´echir `a la complexit´e de ses programmes. La fonction rev renvoie sa liste argument `a l’envers. Ici, l’analyse de complexit´e nous sugg`ere un programme fonctionnel bien meilleur. On ´ecrit une version tr`es na¨ıve de rev en se basant sur le raisonnement suivant : • Si la liste est vide, son « envers » est aussi vide. • Si la liste n’est pas vide, il suffit d’ajouter son premier ´el´ement `a la fin du reste de la liste `a l’envers. Cela se traduit imm´ediatement par la d´efinition : 102 Listes # let rec rev = function | [] -> [] | x :: l -> concat`ene (rev l) [x];; rev : ’a list -> ’a list = # rev [1; 2; 3];; - : int list = [3; 2; 1] Cette version est encore plus jolie avec la version infixe pr´ed´efinie de concat`ene, l’op´erateur @. # let rec rev = function | [] -> [] | x :: l -> rev l @ [x];; rev : ’a list -> ’a list = Cet algorithme est simple, le programme extrˆemement concis, mais malheureusement inefficace, car on ne cesse de recopier des listes en utilisant la fonction concat`ene. Suivons le d´eroulement de l’´evaluation du renversement de la liste [1; 2; 3] : rev [1 ; 2 ; 3] → concat`ene (rev [2 ; 3]) [1] → concat`ene (concat`ene (rev [3]) [2]) [1] → concat`ene (concat`ene (concat`ene (rev []) [3]) [2]) [1] → concat`ene (concat`ene (concat`ene [] [3]) [2]) [1] → concat`ene (concat`ene [3] [2]) [1] → concat`ene (3 : : [2]) [1] → concat`ene [3 ; 2] [1] → 3 : : 2 : : [1] → [3 ; 2 ; 1] Il apparaˆıt que le premier ´el´ement de la liste argument, 1, a ´et´e « cons´e » une fois (pour fabriquer la liste [1]). Le second, 2, a ´et´e « cons´e » deux fois (une fois pour fabriquer la liste [2] et une fois pour obtenir la liste interm´ediaire [2; 1]). Le troisi`eme, 3, a ´et´e « cons´e » trois fois. On montre facilement que si la liste l a n ´el´ements, cet algorithme cr´ee une cellule de liste pour le premier ´el´ement, deux pour le second, . . . , et finalement n cellules pour le dernier ´el´ement. Le nombre total de cellules cr´ees est donc encore la somme 1 + 2 + 3 + · · · + n, qui vaut n × (n + 1)/2 : l’algorithme est quadratique. Le ph´enom`ene de copie r´ecursive des r´esultats partiels conduisant `a un algorithme en n2 est le mˆeme que pour la fonction implose. Pour am´eliorer cet algorithme, il faut utiliser une m´ethode tr`es g´en´erale : pour construire directement le r´esultat, on ajoute un argument suppl´ementaire `a la fonction. Cet argument joue le rˆole d’un accumulateur, car on y m´emorise les r´esultats partiels de la fonction lors des appels r´ecursifs. Cela conduit `a ´ecrire une fonction elle aussi plus g´en´erale, dans la mesure o`u il est possible maintenant de l’appeler avec un accumulateur non vide au d´epart. Prenant un peu de recul, on d´efinit donc une fonction auxiliaire concat`ene_`a_l’envers, qui ´etant donn´es une liste et un accumulateur, recopie la liste en tˆete de l’accumulateur : # let rec concat`ene_`a_l’envers accu = function | [] -> accu | x :: l -> concat`ene_`a_l’envers (x :: accu) l;; concat`ene_`a_l’envers : ’a list -> ’a list -> ’a list = Listes et r´ecurrence 103 # concat`ene_`a_l’envers [0] [1; 2; 3];; - : int list = [3; 2; 1; 0] Illustrons graphiquement le comportement de cette fonction. On part d’une liste l et d’un accumulateur accu qui contient ´eventuellement d´ej`a des ´el´ements : :: :: :: 1 2 3 [] l :: x1 . . . . . . [] accu Puis on recopie la tˆete de la liste l dans l’accumulateur, obtenant : :: 2 :: 3 [] reste l :: :: . . . 1 x1 . . . [] accu `A l’´etape suivante on aura : :: 3 [] reste(reste l) :: :: :: . . . 2 1 x1 . . . [] accu Il est clair maintenant que l’accumulateur engrange les ´el´ements de la liste l `a l’envers. La fonction rev s’en d´eduit simplement, en appelant concat`ene_`a_l’envers avec un accumulateur vide : # let rev l = concat`ene_`a_l’envers [] l;; rev : ’a list -> ’a list = # rev [1; 2; 3];; - : int list = [3; 2; 1] Notre algorithme est maintenant lin´eaire. Cette m´ethode d’accumulation des r´esultats interm´ediaires dans un argument suppl´ementaire de la fonction est souvent une bonne piste `a suivre pour optimiser une fonction, quand cette fonction pose des probl`emes d’efficacit´e. Encore faut-il prouver que la fonction sujette `a optimisation est r´eellement le goulet d’´etranglement du programme, puis d´emontrer par une ´etude de complexit´e que l’optimisation va vraiment am´eliorer les performances. C’est ´evidemment tr`es difficile. En r`egle g´en´erale, on se contentera d’´ecrire des programmes corrects et lisibles. 5.11 Listes et r´ecurrence Nous montrons dans cette section comment prouver des propri´et´es sur les listes. Bien qu’un peu th´eorique, ce n’est pas tr`es complexe, puisqu’il s’agit d’une extension simple du principe de r´ecurrence. 104 Listes Lorsque nous avons ´ecrit des fonctions r´ecursives sur les entiers, nous apportions la preuve de leurs propri´et´es en utilisant le principe de r´ecurrence. Mais ce principe ne s’applique plus dans le cas des listes, puisqu’il concerne uniquement les propri´et´es d´efinies sur les nombres entiers. Comment prouver des propri´et´es des listes ? Il suffit de se ramener au cas des entiers en raisonnant sur le nombre entier qui mesure la longueur de la liste. Le principe de r´ecurrence nous permet alors de d´emontrer qu’une propri´et´e est vraie pour des listes de n’importe quelle longueur, donc pour toutes les listes. Une application directe du principe de r´ecurrence nous permet ainsi d’´etablir que : Si une propri´et´e P est vraie pour une liste de longueur 0, et si d`es qu’elle est vraie pour une liste de longueur n elle est vraie pour une liste de longueur n + 1, alors P est vraie pour des listes de n’importe quelle longueur. Si l’on remarque qu’il n’existe qu’une seule liste de longueur 0, la liste vide, et qu’une liste de longueur n + 1 s’obtient forc´ement en rajoutant un ´el´ement `a une liste de longueur n, on obtient maintenant l’´enonc´e : Si une propri´et´e P est vraie pour [] et si d`es que P est vraie pour l alors P est vraie pour x:: l, alors P est vraie pour toutes les listes. C’est ce qu’on appelle le principe de r´ecurrence structurelle sur les listes. Remarquons que ces deux cas, [] et x:: l, sont justement les deux cas du filtrage d’une fonction r´ecursive sur les listes. Cela justifie nos raisonnements informels pr´ec´edents, quand nous disions « un petit appel r´ecursif et le tour est jou´e », ou bien que nous appelions r´ecursivement une fonction pas encore ´ecrite en pensant « qu’elle saurait bien faire toute seule ». Cela justifie aussi le qualificatif « structurelle » de ce principe de r´ecurrence, puisqu’on raisonne en fait sur la structure des listes. Prouvons par exemple que la fonction ins`ere, qui nous a servi de fonction auxiliaire pour le tri par insertion, ins`ere correctement un ´el´ement dans une liste. Nous consid´erons donc la propri´et´e P(l) suivante : sous l’hypoth`ese que l est une liste bien tri´ee, ins`ere ´el´ement l est une liste, elle aussi bien tri´ee, qui comprend ´el´ement et tous les ´el´ements de la liste l. 1. P est vraie pour []. En effet, ins`ere ´el´ement []vaut [´el´ement], qui est forc´ement bien tri´ee, comprend ´el´ement et tous les ´el´ements de la liste vide. 2. Supposons P(l) vraie. Alors P(x:: l) est vraie aussi. En effet, d’apr`es la d´efinition de la fonction ins`ere, si ´el´ement ≤ x alors ins`ere ´el´ement (x:: l) vaut ´el´ement::x:: l, qui contient ´el´ement et tous les ´el´ements de la liste argument x:: l, et ce r´esultat est bien tri´e puisque x:: l est bien tri´ee par hypoth`ese et que ´el´ement ≤ x. Dans le cas o`u ´el´ement > x, alors ins`ere ´el´ement (x:: l) vaut x:: (ins`ere ´el´ement l). Cette liste est bien tri´ee car, d’apr`es l’hypoth`ese de r´ecurrence, P(l) est vraie, donc (ins`ere ´el´ement l) est bien tri´ee ; mais x est le plus petit ´el´ement de x:: (ins`ere ´el´ement l), puisque c’´etait d´ej`a le plus petit ´el´ement de (x:: l) et qu’il est plus petit que ´el´ement. De plus la liste x:: (ins`ere ´el´ement l) contient ´el´ement et tous les ´el´ements de (x:: l) car elle contient ´evidemment x et par hypoth`ese de r´ecurrence (ins`ere ´el´ement l) contient tous les ´el´ements de l et l’´el´ement `a ins´erer ´el´ement. En conclusion, notre fonction ins`ere fonctionne : P est vraie pour toute liste. Donc, si l est une liste bien tri´ee, ins`ere ´el´ement l est bien tri´ee et comprend ´el´ement en plus `A la recherche de l’it´erateur unique 105 de tous les ´el´ements de la liste l. Les propri´et´es des fonctions d´efinies sur les listes se d´emontreront toujours de fa¸con analogue, en suivant le filtrage utilis´e par la fonction pour diriger la preuve par induction structurelle (induction signifie d´emonstration par r´ecurrence). 5.12 `A la recherche de l’it´erateur unique Nous avons r´eussi `a exprimer la fonction implose en fonction de it_list et de list_it. La question se pose donc du choix de l’it´erateur le plus efficace et de l’utilit´e d’en avoir deux. R´ecursivit´e terminale En ce qui concerne l’efficacit´e, it_list est l´eg`erement plus efficace que list_it, car il est r´ecursif terminal, ce qui signifie qu’il peut s’impl´ementer en machine par un simple saut : il ne laisse pas de calculs en suspens. Par exemple, la fonctionnelle do_list est r´ecursive terminale : # let rec do_list f = function | [] -> () | x :: l -> f x; do_list f l;; do_list : (’a -> ’b) -> ’a list -> unit = En effet, apr`es avoir ex´ecut´e f x on rappelle directement do_list en oubliant le calcul pr´ec´edent. En revanche, map n’est pas r´ecursive terminale : # let rec map f = function | [] -> [] | x :: l -> f x :: map f l;; map : (’a -> ’b) -> ’a list -> ’b list = Lors de l’appel r´ecursif map f l, il faut sauvegarder quelque part la valeur de f x pour l’ajouter en tˆete du r´esultat de map f l. G´en´eralement, ce r´esultat interm´ediaire est sauvegard´e dans un tableau en attendant le retour de l’appel r´ecursif. Ce tableau est appel´e pile d’ex´ecution du programme. Comme toutes les ressources m´emoire, la pile est de taille finie et une fonction qui travaille en espace de pile constant comme do_list est pr´ef´erable `a une fonction qui consomme de la pile comme map. Lorsqu’on a le choix entre une fonction r´ecursive terminale et une autre qui ne l’est pas, on pr´ef`ere g´en´eralement celle qui est r´ecursive terminale, pourvu qu’elle reste simple : rendre une r´ecursion terminale ne justifie g´en´eralement pas qu’on complique le programme. It´erateurs et effets L’it´erateur list_it est tr`es puissant : en ce qui concerne les calculs proprement dits, il n’est pas n´ecessaire d’en avoir d’autre. Par exemple, map s’´ecrit tr`es facilement avec list_it. # let map f l = list_it (function x -> function res -> f x :: res) l [];; map : (’a -> ’b) -> ’a list -> ’b list = 106 Listes # map successeur [1; 2; 3];; - : int list = [2; 3; 4] Dans le mˆeme esprit il serait tentant d’´ecrire do_list en fonction de list_it. # let do_list f l = list_it (function x -> function y -> f x; y) l ();; do_list : (’a -> ’b) -> ’a list -> unit = # do_list print_int [1; 2; 3];; 321- : unit = () La fonction est bien appliqu´ee sur tous les ´el´ements de la liste, mais `a l’envers. Les effets se produisent donc dans l’ordre inverse de la vraie fonction do_list. Cependant, il suffit d’exprimer do_list en fonction de it_list pour que tout rentre dans l’ordre. # let do_list f l = it_list (function y -> function x -> f x; y) () l;; do_list : (’a -> ’b) -> ’a list -> unit = # do_list print_int [1; 2; 3];; 123- : unit = () Tentons alors d’exprimer ´egalement map en fonction de it_list. # let map f l = it_list (function res -> function x -> f x :: res) [] l;; map : (’a -> ’b) -> ’a list -> ’b list = Malheureusement, la liste r´esultat n’est pas dans le bon ordre. # map successeur [1; 2; 3];; - : int list = [4; 3; 2] # map (function x -> print_int x; successeur x) [1; 2; 3];; 123- : int list = [4; 3; 2] En effet, it_list accumule les r´esultats dans la liste res en les ajoutant en tˆete de liste. Comme dans le cas de rev, on obtient ainsi la liste des r´esultats `a l’envers. D’ailleurs, si l’on ne fait qu’accumuler les ´el´ements sur la liste des r´esultats pr´ec´edents, on obtient effectivement une autre version de rev : # let rev l = it_list (function res -> function x -> x :: res) [] l;; rev : ’a list -> ’a list = # rev [1; 2; 3];; - : int list = [3; 2; 1] Cette version peu commune de rev est ´egalement lin´eaire et r´ecursive terminale. En conclusion, on constate que map et do_list sont des versions sp´ecialis´ees d’it´erateurs plus g´en´eraux. Ces fonctions gardent cependant leur int´erˆet, car elles sont simples `a employer et `a comprendre. Du point de vue purement calculatoire, it_list et list_it sont un tant soit peu redondants, mais ils se distinguent lorsque les fonctions qu’ont leur applique produisent des effets. On constate une fois de plus que les effets compliquent les choses, car ils permettent de distinguer plus finement le comportement d’algorithmes math´ematiquement ´equivalents. On peut s’en r´ejouir ou en avoir peur . . . `A la recherche de l’it´erateur unique 107 l 6 Les structures de donn´ees O`u l’on apprend `a m´elanger les torchons et les serviettes pour d´efinir le linge de maison. n Caml, les types de donn´ees comprennent principalement les types somme et les types produit, c’est-`a-dire les types « ou » et les types « et », encore appel´es les ´enum´erations g´en´eralis´ees et les enregistrements, ou plus techniquement encore l’union disjointe et les produits `a champs nomm´es. Dans ce chapitre, nous introduisons ces diff´erents types de donn´ees et les montrons `a l’oeuvre sur le probl`eme de la repr´esentation efficace des polynˆomes. 6.1 Polynˆomes pleins et polynˆomes creux Nous avons vu par deux fois des calculs sur les polynˆomes, d’abord repr´esent´es par des tableaux dans le chapitre 3 (section 3.3), puis comme des listes dans le chapitre 5 (section 5.5). Nous avons appel´e les seconds polynˆomes creux, les premiers polynˆomes pleins. Maintenant se pose ´evidemment le probl`eme de travailler avec ces deux repr´esentations en mˆeme temps, pour b´en´eficier des avantages de chacune d’elles : lorsqu’un polynˆome est plein, la repr´esentation `a l’aide d’un tableau est ´economique, car les degr´es sont implicites ; en revanche, lorsqu’un polynˆome est creux (comporte beaucoup de coefficients nuls), la repr´esentation en liste est pr´ef´erable — quand elle n’est pas tout simplement la seule envisageable, comme pour le polynˆome 1+x1000000. Nous aimerions donc repr´esenter les polynˆomes par un tableau ou une liste selon le cas, mais d´efinir des op´erations qui travaillent indiff´eremment sur l’une ou l’autre des repr´esentations. Or, ces deux types de repr´esentations sont incompatibles au point de vue du typage. Consid´erons la proc´edure d’impression des polynˆomes : nous avons d´efini deux fonctions, sp´ecifiques `a chacune des repr´esentations, imprime_polyn^ome_plein : int vect -> unit, qui imprime les polynˆomes pleins, et imprime_polyn^ome_creux : (int * int) list -> unit, qui imprime les polynˆomes creux. Pour avoir une primitive d’impression travaillant sur tous les polynˆomes, on aurait donc envie d’´ecrire : let imprime_polyn^ome p = if p « est un polynˆome plein » then imprime_polyn^ome_plein p 110 Les structures de donn´ees else imprime_polyn^ome_creux p;; C’est effectivement la bonne id´ee, mais il faut la raffiner un peu : outre qu’on ne voit pas comment impl´ementer le pr´edicat « est un polynˆome plein », il se pose ´egalement un probl`eme de typage pour l’argument p de imprime_polyn^ome : est-ce une liste comme le sugg`ere l’appel de fonction imprime_polyn^ome_creux p, ou un tableau pour pouvoir ˆetre pass´e en argument `a imprime_polyn^ome_plein ? On obtiendrait forc´ement une erreur de typage. Par exemple, en supposant que « est un polynˆome plein » renvoie toujours la valeur true : # let imprime_polyn^ome p = if true then imprime_polyn^ome_plein p else imprime_polyn^ome_creux p;; Entr´ee interactive: > else imprime_polyn^ome_creux p;; > ^ Cette expression est de type int vect, mais est utilis´ee avec le type (int * int) list. Il faut donc m´elanger les polynˆomes creux et pleins au sein d’un mˆeme type qui les comprenne tous les deux. Le type polyn^ome On d´efinit donc un nouveau type, polyn^ome, qui ´etablit explicitement le m´elange : il indique qu’il comprend deux cas possibles, le cas des polynˆomes pleins qui seront des tableaux d’entiers et le cas des polynˆomes creux qui seront des listes de paires d’entiers. # type polyn^ome = | Plein of int vect | Creux of (int * int) list;; Le type polyn^ome est d´efini. Le mot-cl´e type introduit la d´efinition du nouveau type polyn^ome. Apr`es le signe =, on ´ecrit la liste des possibilit´es du type en cours de d´efinition. Les noms Plein et Creux sont appel´es les constructeurs de valeurs du type (s’il n’y a pas d’ambigu¨ıt´e on dit simplement « constructeurs »). Comme d’habitude, la barre verticale | indique l’alternative et se lit « ou ». Le mot-cl´e of indique le type de l’argument du constructeur. Le type polyn^ome comprenant les valeurs d’un type plus les valeurs d’un autre type, on dit que c’est un type somme. On peut maintenant cr´eer des valeurs de type polyn^ome en appliquant l’un des deux constructeurs du type polyn^ome `a une valeur du type correspondant. Par exemple : # let p1 = Plein [|1; 2; 3|];; p1 : polyn^ome = Plein [|1; 2; 3|] # let p2 = Creux [(1, 0); (1, 100)];; p2 : polyn^ome = Creux [1, 0; 1, 100] Maintenant p1 et p2 sont du mˆeme type et pourront ˆetre arguments d’une mˆeme fonction. Polynˆomes pleins et polynˆomes creux 111 Le filtrage est ´etendu `a tous les types somme et permet, ´etant donn´ee une valeur du type somme, de d´eterminer dans quel cas se trouve cette valeur. Pour le type polyn^ome, le filtrage va donc nous permettre d’impl´ementer la fonction « est un polynˆome plein » : # let est_un_polyn^ome_plein = function | Plein _ -> true | Creux _ -> false;; est_un_polyn^ome_plein : polyn^ome -> bool = Une fonction travaillant sur des valeurs de type polyn^ome fera typiquement une discrimination sur les valeurs du type par un filtrage du genre : let f = function | Plein v -> ... | Creux l -> ...;; Remarquez que le filtrage permet `a la fois de d´eterminer le type du polynˆome et de r´ecup´erer son tableau ou sa liste de monˆomes. C’est strictement analogue au cas des listes o`u nous ´ecrivions : let f = function | [] -> ... | x :: reste -> ...;; C’est maintenant un jeu d’enfant que d’´ecrire la fonction d’impression des valeurs de type polyn^ome : # let imprime_polyn^ome = function | Plein v -> imprime_polyn^ome_plein v | Creux l -> imprime_polyn^ome_creux l;; imprime_polyn^ome : polyn^ome -> unit = # imprime_polyn^ome p1;; 1 + 2x + 3x^2- : unit = () # imprime_polyn^ome p2;; 1 + x^100- : unit = () Op´erations sur les valeurs de type polyn^ome Nous d´efinissons l’addition et la multiplication des polynˆomes creux ou pleins. Puisque les polynˆomes se pr´esentent sous deux formes, nous avons quatre cas `a envisager. L’id´ee est simple : • la somme de deux polynˆomes creux est un polynˆome creux : on appelle l’addition des polynˆomes creux ; • la somme de deux polynˆomes pleins est un polynˆome plein : on appelle l’addition des polynˆomes pleins ; • la somme de deux polynˆomes d’esp`eces diff´erentes est un polynˆome creux. En effet, si l’un des polynˆomes est creux il comprend beaucoup de z´eros et sa somme avec un autre polynˆome comprendra aussi beaucoup de z´eros en g´en´eral (consid´erez par exemple (1+x+3x2)+(1+x100)). Donc, dans le cas mixte, nous appelons encore l’addition des polynˆomes creux. Puisque l’un des polynˆomes est plein, nous avons besoin d’une fonction qui transforme un polynˆome plein en polynˆome creux. C’est sans 112 Les structures de donn´ees difficult´e : nous parcourons le tableau des coefficients en accumulant dans une liste les monˆomes rencontr´es. La seule subtilit´e est de parcourir le tableau `a l’envers pour que le dernier monˆome ajout´e `a la liste soit bien celui de degr´e 0. # let plein_vers_creux v = let l = ref [] in for i = vect_length v - 1 downto 0 do if v.(i) <> 0 then l := (v.(i), i) :: !l done; !l;; plein_vers_creux : int vect -> (int * int) list = L’addition des polynˆomes se d´efinit alors tr`es simplement : # let ajoute_polyn^omes p1 p2 = match p1, p2 with | Plein v, Plein v’ -> Plein (ajoute_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (ajoute_polyn^omes_creux l l’) | Plein v, Creux l -> Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v -> Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l);; ajoute_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = Ce code peut ˆetre l´eg`erement simplifi´e en remarquant que les deux derniers cas du filtrage sont presque identiques (ces deux cas se traduisent par deux clauses du filtrage dont la partie expression est la mˆeme). Pour ´eviter cette redite, on joue sur le fait que l’addition des polynˆomes est commutative pour traiter le dernier cas par un appel r´ecursif `a la fonction ajoute_polyn^ome qui inverse les arguments p1 et p2. # let rec ajoute_polyn^omes p1 p2 = match p1, p2 with | Plein v, Plein v’ -> Plein (ajoute_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (ajoute_polyn^omes_creux l l’) | Plein v, Creux l -> Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v -> ajoute_polyn^omes p2 p1;; ajoute_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = Cette derni`ere solution permet de ne pas dupliquer de code, ce qui raccourcit l´eg`erement le texte de la fonction et diminue la probabilit´e d’introduire une erreur en ne modifiant qu’une des clauses lors de corrections ult´erieures du programme. En fait, lorsque l’expression `a renvoyer est compliqu´ee, l’appel r´ecursif s’impose sans contestation possible. Cependant, cette solution pr´esente l’inconv´enient de sugg´erer que la fonction ajoute_polyn^ome est vraiment r´ecursive, alors qu’elle ne l’est que pour des raisons « administratives ». La multiplication n’est pas plus compliqu´ee : # let rec multiplie_polyn^omes p1 p2 = match p1, p2 with | Plein v, Plein v’ -> Plein (multiplie_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (multiplie_polyn^omes_creux l l’) | Plein v, Creux l -> Types sommes ´elabor´es 113 Creux (multiplie_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v -> multiplie_polyn^omes p2 p1;; multiplie_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = # imprime_polyn^ome (multiplie_polyn^omes p1 p2);; 1 + 2x + 3x^2 + x^100 + 2x^101 + 3x^102- : unit = () # let p10000 = Creux [(1, 0); (1, 10000)];; p10000 : polyn^ome = Creux [1, 0; 1, 10000] # imprime_polyn^ome (multiplie_polyn^omes p10000 p10000);; 1 + 2x^10000 + x^20000- : unit = () 6.2 Types sommes ´elabor´es Un autre exemple classique de type somme est la mod´elisation des peintures. On suppose que les peintures sont d´ecrites soit par un nom explicite, soit par un simple num´ero de r´ef´erence, soit par un m´elange d’autres peintures. Nous envisagerons successivement ces trois cas et construirons donc le type peinture par raffinements successifs, en trois ´etapes. ´E num´erations On consid`ere d’abord les peintures explicitement nomm´ees, en supposant qu’il en existe trois : le Bleu, le Blanc et le Rouge. Le type peinture comporte donc trois cas : c’est un type somme. Ces cas ne sont plus des valeurs de types diff´erents comme pour les polynˆomes, mais simplement trois constantes. On les mod´elise par trois constructeurs sans arguments, donc sans partie of dans la d´efinition : # type peinture = | Bleu | Blanc | Rouge;; Le type peinture est d´efini. Les trois constructeurs sont maintenant trois nouvelles constantes du langage Caml, de type peinture. # let p = Bleu;; p : peinture = Bleu Tout naturellement, le filtrage s’applique aussi `a ce nouveau type : # let est_blanche = function | Blanc -> true | _ -> false;; est_blanche : peinture -> bool = # est_blanche p;; - : bool = false Ces types somme ne comportant que des constantes sont appel´es types ´enum´er´es. Vous en connaissez d´ej`a : par exemple, le type bool est un type somme ´enum´er´e `a deux constantes, true et false. 114 Les structures de donn´ees Types `a constructeurs non constants Nous supposons maintenant qu’il existe dans l’ensemble de toutes les peintures des teintes qui n’ont pas de nom, mais seulement un num´ero de r´ef´erence. Nous ´etendons donc le type peinture avec un nouveau constructeur qui prenne en compte ce cas. Il s’agit maintenant d’un constructeur ayant un argument : le num´ero de r´ef´erence. Appelons ce constructeur Num´ero. Par exemple, Num´ero 14 mod´elisera la peinture de r´ef´erence num´ero 14. Nous d´efinissons donc le nouveau type des peintures comme : # type peinture = | Bleu | Blanc | Rouge | Num´ero of int;; Le type peinture est d´efini. Types r´ecursifs La prochaine ´etape est la description des m´elanges de peintures. Il existe maintenant des peintures qui sont simplement des m´elanges de deux autres peintures (en proportions ´egales) et qu’on identifie par les peintures qui les composent. Nous introduisons donc un nouveau constructeur M´elange avec pour argument un couple de peintures. Notre type devient : # type peinture = | Bleu | Blanc | Rouge | Num´ero of int | M´elange of peinture * peinture;; Le type peinture est d´efini. # let m´el1 = M´elange (Bleu, Blanc);; m´el1 : peinture = M´elange (Bleu, Blanc) # let m´el2 = M´elange (Num´ero 0, Rouge);; m´el2 : peinture = M´elange (Num´ero 0, Rouge) Remarquez que le type peinture est devenu r´ecursif, puisqu’il intervient dans sa propre d´efinition. Ainsi, on peut m´elanger n’importe quelles peintures et en particulier faire des m´elanges de plus de deux peintures. # let m´el3 = M´elange (m´el1,m´el2);; m´el3 : peinture = M´elange (M´elange (Bleu, Blanc), M´elange (Num´ero 0, Rouge)) Le filtrage sur le type peinture ne pose pas de probl`emes : # let rec contient_du_bleu = function | Bleu -> true | M´elange (p1,p2) -> contient_du_bleu p1 || contient_du_bleu p2 | _ -> false;; contient_du_bleu : peinture -> bool = # contient_du_bleu m´el3;; - : bool = true Types sommes ´elabor´es 115 La d´efinition du type peinture, quoique r´ecursive, conserve tout de mˆeme un sens, parce qu’il existe des cas de base pour arrˆeter la r´ecursion. C’est tout `a fait analogue aux d´efinitions de fonctions r´ecursives qui pr´esentent des cas d’arrˆet simples. Les cas de base du type, comme par exemple les constructeurs sans arguments, correspondent souvent `a des cas de base des fonctions r´ecursives sur ce type. Les cartes On mod´elise tr`es ais´ement un jeu de cartes en utilisant les types somme. Les couleurs forment un type ´enum´er´e : # type couleur = | Tr`efle | Carreau | Coeur | Pique;; Le type couleur est d´efini. et les cartes un type somme `a plusieurs possibilit´es, selon les valeurs faciales des cartes : # type carte = | As of couleur | Roi of couleur | Dame of couleur | Valet of couleur | Petite_carte of int * couleur;; Le type carte est d´efini. Dans cette d´efinition, nous avons choisi de regrouper toutes les cartes qui ne sont pas des figures sous la mˆeme d´enomination : Petite_carte. On aurait pu aussi continuer l’´enum´eration avec des constructeurs Dix, Neuf, Huit, etc. Pour illustrer le filtrage sur les types somme, nous d´efinissons la valeur d’une carte `a la « belote ». Cette valeur d´epend d’une couleur particuli`ere, l’atout, choisie par les joueurs `a chaque tour. Les cartes dont la valeur change sont le valet et le neuf : le neuf compte d’ordinaire pour 0, mais vaut 14 quand il est de la couleur de l’atout, et le valet d’atout vaut 20 au lieu de 2 d’ordinaire. D’autre part, les dix valent 10 points et les autres petites cartes 0. # let valeur_d’une_carte couleur_d’atout = function | As _ -> 11 | Roi _ -> 4 | Dame _ -> 3 | Valet c -> if c = couleur_d’atout then 20 else 2 | Petite_carte (10, _) -> 10 | Petite_carte (9, c) -> if c = couleur_d’atout then 14 else 0 | _ -> 0;; valeur_d’une_carte : couleur -> carte -> int = Remarquez que la structure du filtrage de la fonction valeur_d’une_carte est tr`es similaire `a la d´efinition du type carte. C’est un m´ecanisme fr´equent en Caml : pour d´efinir une fonction sur un type somme, on se guide souvent sur la d´efinition du type qui donne le squelette du filtrage `a utiliser. On le compl`ete alors pour envisager les cas particuliers, comme ici les cas du 10 et du 9. Cela termine les exemples de types somme. Nous donnons maintenant une pr´esentation plus g´en´erale du concept. 116 Les structures de donn´ees 6.3 Les types somme Les types somme servent donc `a mod´eliser des donn´ees comprenant des alternatives. On les appelle aussi types « ou », car une donn´ee mod´elis´ee par un type somme est d’une esp`ece ou d’une autre ou . . . Par exemple, une peinture est soit nomm´ee, soit un simple num´ero, soit un m´elange de deux peintures ; de mˆeme, les polynˆomes sont soit pleins, soit creux. Les fonctions d´efinies sur un type somme op`erent g´en´eralement par filtrage sur les ´el´ements du type ; elles ont une structure tr`es voisine de la d´efinition du type. On peut consid´erer qu’on emploie alors une programmation « dirig´ee par le filtrage » (ou dirig´ee par les d´efinitions de type). Remarquons qu’il est d’usage de toujours mettre une majuscule aux noms des constructeurs de type somme, pour ne pas les confondre avec les noms de variables dans le filtrage. Corr´elativement, il est recommand´e de toujours ´ecrire les noms de variables en minuscules. Cette r`egle n’est pas absolue : par exemple les bool´eens true et false d´erogent `a cette r`egle. Il est vrai que leur statut est tr`es particulier puisque ce sont des constructeurs primitifs et des mots-cl´es du langage. Formellement, les types somme sont l’analogue Caml de la notion math´ematique de somme disjointe d’ensemble. Nous n’´etudierons pas cette notion, nous contentant de l’id´ee intuitive ci-dessus. 6.4 Les types produit Caml offre une deuxi`eme classe de structures de donn´ees, compl´ementaires des types somme : les types produit, encore appel´es enregistrements ou records en anglais. D´efinition des types enregistrement Les enregistrements constituent une g´en´eralisation des n-uplets. Ils servent `a mod´eliser les donn´ees qui ont simultan´ement plusieurs propri´et´es. On les appelle donc aussi types « et », car ils s’utilisent pour repr´esenter une donn´ee ayant telle caract´eristique et telle autre caract´eristique et . . . On dresse alors la liste des caract ´eristiques lors de la d´efinition du type. Chacune des caract´eristiques est accompagn´ee de son type respectif et la liste de toutes les caract´eristiques est entour´ee d’accolades { }. Par exemple, un monˆome poss`ede un coefficient et un degr´e ; pour mod´eliser les monˆomes, nous d´efinirons donc un type produit avec deux caract´eristiques, coefficient et degr´e, toutes deux de type entier. # type mon^ome = { coefficient : int; degr´e : int };; Le type mon^ome est d´efini. Pour construire une valeur de ce type, on se contente d’´enum´erer ses caract´eristiques particuli`eres : # let m1 = {coefficient = 1; degr´e = 0};; m1 : mon^ome = {coefficient = 1; degr´e = 0} Les types produit 117 Acc`es aux composantes des enregistrements Pour acc´eder `a l’une des caract´eristiques d’un objet de type produit, il suffit de faire suivre l’objet d’un point « . » et du nom de la caract´eristique. C’est analogue au « . » de l’acc`es dans les tableaux et les chaˆınes de caract`eres. # m1.coefficient;; - : int = 1 # m1.degr´e;; - : int = 0 Ce que nous avons appel´e « nom de caract´eristique » porte le nom technique d’´etiquette. Une caract´eristique s’appelle aussi une rubrique, ou encore un champ de l’enregistrement. Les ´etiquettes permettent de se lib´erer de l’ordre dans lequel on nomme les caract´eristiques : le compilateur se charge de les mettre dans le bon ordre. Ainsi, si l’on intervertit l’ordre des ´etiquettes, la valeur produite est la mˆeme : # let m2 = { degr´e = 0; coefficient = 1 };; m2 : mon^ome = {coefficient = 1; degr´e = 0} # m1 = m2;; - : bool = true Filtrage des types enregistrement Le filtrage s’´etend naturellement aux enregistrements : # let de_degr´e_z´ero = function | {degr´e = 0; coefficient = _} -> true | _ -> false;; de_degr´e_z´ero : mon^ome -> bool = # de_degr´e_z´ero m1;; - : bool = true Il existe en plus une ellipse sp´eciale pour indiquer qu’on ne s’int´eresse pas au reste des champs d’un enregistrement : on ´ecrit « ; _ » pour dire « quels que soient les autres champs et leurs contenus ». On ´ecrirait donc la fonction de_degr´e_z´ero plus naturellement ainsi : # let de_degr´e_z´ero = function | {degr´e = 0; _} -> true | _ -> false;; de_degr´e_z´ero : mon^ome -> bool = On peut aussi nommer le contenu des champs et faire des synonymes. Par exemple, la fonction qui renvoie la valeur absolue d’un monˆome s’´ecrit : # let abs_mon^ome = function {coefficient = a; degr´e = d} as m -> if a < 0 then {coefficient = -a; degr´e = d} else m;; abs_mon^ome : mon^ome -> mon^ome = Les types enregistrement portent le nom technique de produits nomm´es `a champs nomm´es, et les types produit en g´en´eral correspondent `a la notion math´ematique de produit cart´esien d’ensembles. Encore une fois, nous nous contenterons de la vision intuitive. 118 Les structures de donn´ees 6.5 M´elange de types somme et types produit Types somme et types produit peuvent ˆetre arbitrairement m´elang´es. Ainsi, une d´efinition plus ´elabor´ee des polynˆomes serait : # type poly = | Plein of int vect | Creux of mon^ome list;; Le type poly est d´efini. L’avantage de cette d´efinition est d’avoir explicitement nomm´e les caract´eristiques d’un monˆome. Par exemple, la fonction plein_vers_creux deviendrait : # let plein_vers_creux v = let l = ref [] in for i = vect_length v - 1 downto 0 do l := {degr´e = i; coefficient = v.(i)} :: !l done; !l;; plein_vers_creux : int vect -> mon^ome list = On a donc remplac´e la paire (v.(i), i) par l’enregistrement {degr´e = i; coefficient = v.(i)} dans le code de l’ancienne fonction. La diff´erence est faible mais elle suffit `a rendre le code plus facile `a lire et `a ´ecrire. D’ailleurs la premi`ere version de plein_vers_creux ´ecrite au cours de la r´edaction de ce livre ´etait fausse : au lieu de (v.(i), i) nous avions ´ecrit (i, v.(i)) parce que nous avions oubli´e dans quel ordre nous avions d´ecid´e de mettre le degr´e et le coefficient dans les couples d’entiers mod´elisant les monˆomes ! Cela ne se voyait pas sur le type des polynˆomes creux, la convention ´etant simplement r´epartie dans le code des fonctions de manipulation des polynˆomes creux. Cette erreur, ind´etectable par typage, est facilement ´evit´ee quand on ´ecrit explicitement les noms degr´e et coefficient et que c’est le compilateur qui r´etablit l’ordre des rubriques. 6.6 Structures de donn´ees mutables Nous connaissons d´ej`a certaines structures de donn´ees dont le contenu est modifiable dynamiquement : ce sont les r´ef´erences et les tableaux. Les enregistrements poss`edent aussi cette qualit´e : lors de la d´efinition d’un type enregistrement, certains champs peuvent ˆetre qualifi´es de «mutables », c’est-`a-dire modifiables. Le v´erificateur de type autorise alors la modification physique du contenu du champ des objets de ce type. Enregistrements `a champs mutables Voici un exemple tr`es simple o`u il est n´ecessaire de modifier des champs d’enregistrement : supposez qu’on veuille mod´eliser des comptes bancaires. En premi`ere approximation, un compte se caract´erise par son num´ero et par le montant actuel des d´epˆots sur le compte (le solde du compte). Si le num´ero du compte a peu de chance de changer, en revanche le solde varie `a chaque retrait ou d´epˆot. Il faut donc que l’´etiquette solde du type compte soit d´eclar´ee mutable `a la d´efinition du type. Structures de donn´ees mutables 119 # type compte = { num´ero : int; mutable solde : float };; Le type compte est d´efini. La d´efinition d’une valeur d’un type enregistrement `a champs mutables et l’acc`es `a ses champs ne diff`erent en rien du cas d’un type enregistrement normal. # let compte_de_durand = {num´ero = 0; solde = 1000.0};; compte_de_durand : compte = {num´ero = 0; solde = 1000.0} # compte_de_durand.solde;; - : float = 1000.0 Pour d´efinir la fonction d´ep^ot qui met `a jour le solde d’un compte lors d’un d´epˆot, on utilise l’op´eration de modification physique d’un champ d’enregistrement, not´ee e1.´etiquette <- e2, o`u e1 et e2 sont deux expressions et ´etiquette le nom d’une ´etiquette d’enregistrement. L’ex´ecution de cette expression remplace le contenu du champ ´etiquette de l’enregistrement e1 par la valeur de l’expression e2. Comme toute modification physique, cette expression renvoie la valeur « rien » (). La fonction d´ep^ot s’´ecrit donc simplement : # let d´ep^ot compte montant = compte.solde <- montant +. compte.solde;; d´ep^ot : compte -> float -> unit = # d´ep^ot compte_de_durand 30.0;; - : unit = () # compte_de_durand.solde;; - : float = 1030.0 Variables r´emanentes Cet exemple nous permet aussi d’aborder les r´ef´erences locales aux fonctions qui conservent leur valeur entre les diff´erents appel `a la fonction. Nous d´efinissons la fonction de cr´eation des comptes : elle doit n´ecessairement allouer un nouveau num´ero `a chaque nouveau compte cr´e´e. Pour cela, il lui suffit de tenir `a jour une r´ef´erence enti`ere contenant le dernier num´ero de compte attribu´e et de l’incr´ementer `a chaque cr´eation. Pour s’assurer que cette r´ef´erence ne peut ˆetre modifi´ee par inadvertance dans une autre partie du programme de gestion des comptes, on la rend compl`etement locale au corps de la fonction qui cr´ee les comptes : # let cr´ee_compte = let num´ero_de_compte = ref 0 in (function d´epot -> num´ero_de_compte := !num´ero_de_compte + 1; {num´ero = !num´ero_de_compte; solde = d´epot});; cr´ee_compte : float -> compte = # let compte_de_dupont = cr´ee_compte 500.0;; compte_de_dupont : compte = {num´ero = 1; solde = 500.0} # let compte_de_duval = cr´ee_compte 1000.0;; compte_de_duval : compte = {num´ero = 2; solde = 1000.0} Il faut bien comprendre que la r´ef´erence num´ero_de_compte est cr´e´ee une seule fois, lors de la construction de la fonction cr´ee_compte. `A chaque appel de cr´ee_compte, on retrouve ainsi dans num´ero_de_compte la derni`ere valeur qui y a ´et´e inscrite. Les 120 Les structures de donn´ees variables de ce genre sont appel´ees variables r´emanentes dans la litt´erature informatique (et variables statiques en C). Remarquez que Caml les autorise, sans avoir besoin de fournir une construction sp´eciale pour cela : le let in habituel et les fonctions anonymes suffisent pour programmer des variables r´emanentes. 6.7 Structures de donn´ees et filtrage Comme nous l’avons vu `a maintes reprises, le filtrage va de paire avec les d´efinitions de structures de donn´ees. Bien plus, la d´efinition d’un type sert de guide pour ´ecrire le squelette du filtrage des fonctions qui op`erent sur ce type. Nous voulons cependant attirer votre attention sur quelques traits avanc´es du filtrage et quelques ´ecueils qui guettent les d´ebutants qui ´ecrivent leurs premiers filtrages. Filtrage de valeurs calcul´ees En premier lieu, il faut conserver `a l’esprit que le filtrage en Caml est structurel : on ne peut utiliser dans les motifs que des constructeurs, des constantes et des variables, `a l’exclusion des valeurs calcul´ees. C’est pourquoi les variables qui interviennent dans un motif ne servent jamais `a faire des tests, mais au contraire `a lier des parties de la valeur filtr´ee. Comparer par exemple, la d´efinition (erron´ee) de la fonction est_un avec celle (correcte) de la fonction test_`a_un : # let un = 1;; un : int = 1 # let est_un = function | un -> true | _ -> false;; Entr´ee interactive: > | _ -> false;; > ^ Attention: ce cas de filtrage est inutile. est_un : ’a -> bool = # est_un 2;; - : bool = true # let test_`a_un x = if x = un then true else false;; test_`a_un : int -> bool = # test_`a_un 2;; - : bool = false Le premier filtre de la fonction est_un comprend la variable un, qui est sans rapport avec l’identificateur un pr´ec´edemment d´efini `a la valeur 1. Autrement dit, le nom de la variable un est sans importance dans le filtrage de la fonction est_un : on peut le remplacer par x ou y, et le filtrage est ´equivalent `a x -> true | _ -> false. Contrairement `a ce que l’utilisateur voulait sans doute exprimer, la fonction est_un ne teste donc pas si son argument correspond `a la mˆeme valeur que l’identificateur un : en fait, la fonction est_un renvoie toujours true. Cela explique le r´esultat de est_un 2. Cela explique aussi le message du compilateur : « ce cas de filtrage est inutile ». Le compilateur s’est rendu compte que le cas _ -> ne servira jamais. C’est Structures de donn´ees et filtrage 121 pour ´eviter ce genre de confusions qu’on utilise la convention de faire commencer les noms de constructeurs par une majuscule et d’´ecrire les variables dans les filtres en minuscules. Retenons que Toute variable dans un filtre est une nouvelle variable. Lin´earit´e du filtrage Il faut ´egalement savoir que le filtrage en Caml est lin´eaire, ce qui signifie qu’un nom de variable ne peut apparaˆıtre qu’une seule fois dans un filtre. Cette contrainte est viol´ee le plus souvent lorsqu’on veut tester l’´egalit´e de deux morceaux d’une valeur. Voici une tentative (erron´ee) de d´efinir la fonction d’´egalit´e : si le couple argument comporte deux composantes identiques on renvoie vrai et sinon on renvoie faux. # let ´egal = function | (x, x) -> true | _ -> false;; Entr´ee interactive: > | (x, x) -> true > ^ L’identificateur x est d´efini plusieurs fois dans ce motif. Les tests d’´egalit´e op´er´es par le filtrage ne concernent que les constantes (les constructeurs). Les tests d’´egalit´e plus g´en´eraux ne s’expriment pas par filtrage, ils doivent faire l’objet d’une alternative explicite (un if then else) dans l’expression d’une clause de filtrage ou bien d’une garde que nous ´etudions bri`evement dans le prochain paragraphe. Une d´efinition acceptable de ´egal serait donc : # let ´egal = function (x, y) -> if x = y then true else false;; ´egal : ’a * ’a -> bool = Remarque : comme nous l’avons vu au paragraphe 2.1, l’alternative du corps de ´egal est inutile ; on la remplace donc simplement par sa partie condition pour obtenir # let ´egal (x, y) = x = y;; ´egal : ’a * ’a -> bool = Ce qui nous permet de constater que la fonction ´egal ne d´efinit pas la fonction d’´egalit´e mais est en fait un synonyme de l’op´erateur = (plus pr´ecis´ement ´egal est la version non curryfi´ee de l’op´erateur =). Combiner filtrage et tests : les gardes Vous remarquerez sans doute que le filtrage permet une programmation particuli` erement claire, et autorise l’´ecriture compacte de multiples conditions. Cependant le simple filtrage structurel (et lin´eaire) ne permet pas de m´elanger la s´election sur la forme de la valeur filtr´ee et les tests sur les valeurs effectives des composants du filtre. C’est pourquoi le filtrage de Caml propose une construction suppl´ementaire, les gardes, pour effectuer des tests arbitraires pendant le filtrage. La clause | filtre when condition -> ... 122 Les structures de donn´ees o`u condition est une expression bool´eenne quelconque, filtre les mˆemes valeurs que filtre, mais elle n’est s´electionn´ee que dans le cas o`u condition est vraie ; dans le cas contraire le filtrage continue normalement en s´equence. `A l’aide d’une garde, on ´ecrit facilement une version correcte de la fonction est_un : # let est_un = function | x when x = un -> true | _ -> false;; est_un : int -> bool = # est_un 2;; - : bool = false On ´ecrit aussi la fonction valeur_d’une_carte encore plus ´el´egamment : # let valeur_d’une_carte couleur_d’atout = function | As _ -> 11 | Roi _ -> 4 | Dame _ -> 3 | Valet c when c = couleur_d’atout -> 20 | Valet _ -> 2 | Petite_carte (9, c) when c = couleur_d’atout -> 14 | Petite_carte (10, _) -> 10 | _ -> 0;; valeur_d’une_carte : couleur -> carte -> int = Filtrage exhaustif, filtrage partiel Enfin, il faut se m´efier des filtrages non exhaustifs, c’est-`a-dire des filtrages qui oublient des cas. C’est une des forces du filtrage de servir de guide pour facilement envisager tous les cas concernant une structure de donn´ees, il faut donc en profiter pleinement. Si vous oubliez d’envisager certains cas, le compilateur ´emet un message d’avertissement et il faut en tenir compte. Voici un exemple caricatural de filtrage non exhaustif : # let vide = function [] -> true;; Entr´ee interactive: >let vide = function [] -> true;; > ^^^^^^^^^^^^^^^^^^^ Attention: ce filtrage n’est pas exhaustif. vide : ’a list -> bool = Dans cette situation, il faut vous efforcer de « boucher les trous » de votre filtrage. Laisser des filtrages non exhaustifs dans un programme est g´en´eralement consid´er´e comme un laisser-aller de mauvais aloi. 6.8 Structures de donn´ees et r´ecurrence Cette section peut ˆetre saut´ee en premi`ere lecture. Nous y ´etendons le principe de r´ecurrence aux types de donn´ees. Comme pour les listes, nous aimerions disposer d’un moyen de prouver des propri ´et´es sur les programmes qui utilisent des types somme ou des types produit. Comme Structures de donn´ees et r´ecurrence 123 dans le cas des listes, il suffit de trouver un nombre li´e aux donn´ees, sur lequel on puisse employer le principe de r´ecurrence. Pour les listes, nous avions utilis´e la longueur. En fait, il suffit pour g´en´eraliser `a tous les types somme de remarquer que la longueur d’une liste n’est autre que le nombre d’utilisation du constructeur « :: » n´ecessaire `a la construction de la liste. Pour un type somme g´en´eral, on pourra ainsi raisonner sur le nombre d’occurrences des constructeurs du type. Le cas particulier de la liste vide sera transpos´e `a tous les constructeurs constants du type, tandis que le cas de « :: » correspondra au nombre d’occurrences des constructeurs non constants. Pour les types produit, il y a plusieurs mani`eres d’´etendre le raisonnement par r´ecurrence. Pour simplifier, supposons que nous devions d´emontrer une propri´et´e sur un type produit `a deux composantes seulement et pour simplifier encore supposons que ce soit le type int * int. Pour prouver une propri´et´e P qui d´epend d’une paire d’entiers (m, n), on utilisera par exemple le principe de r´ecurrence suivant : Si P(0, 0) est vraie, et d`es que P(m, n) est vraie alors P(m+1, n) et P(m, n+ 1) sont vraies, alors P est vraie pour tous m et n. Ce principe se d´emontre facilement `a partir du principe de base et se g´en´eralise sans difficult´e `a un nombre quelconque de variables. Cette extension du principe de r´ecurrence aux structures de donn´ees se nomme tout naturellement la r´ecurrence structurelle. C’est le principal moyen de d´emonstration `a la disposition de l’informaticien. Il est heureux qu’il soit si simple `a appr´ehender. 7 Le docteur O`u l’intelligence naturelle vient au secours de la bˆetise artificielle. n vous initie ici au maniement des exceptions, des listes d’association et des entr´ees-sorties ´el´ementaires de Caml. Par ailleurs, on impl´emente un programme qui essaye de se faire passer pour un psychanalyste . . . 7.1 Vue d’ensemble Notre « psychanalyste » ´electronique s’appelle Cam´elia. C’est un programme interactif, qui pose des questions `a son « patient » et r´eagit `a ses r´eponses en imprimant un message au terminal. La strat´egie du programme repose donc sur l’examen des phrases entr´ees par le patient. Une fois la phrase examin´ee, le programme choisit au hasard une r´eponse parmi un ensemble de r´eponses toutes faites, pr´epar´ees par le programmeur. L’examen d’une phrase consiste simplement `a chercher des mots connus du programme dans la phrase qui lui est soumise. Les phrases sont class´ees sommairement en trois cat´egories : les phrases tr`es simples (par exemple r´eduites `a « oui » ou « non »), pour lesquelles Cam´elia dispose d’un ensemble de r´eponses types ; les questions (phrases termin´ees par un caract`ere « ? ») auxquelles Cam´elia r´epond «C’est moi qui pose les questions » ou une phrase du mˆeme genre ; et les phrases complexes, pour lesquelles Cam´elia cherche un mot int´eressant (comme « famille » ou « p`ere » ou « sexe »). Si le programme trouve un mot int´eressant, il r´eagit par une phrase type (toujours tir´ee au hasard parmi un ensemble pr´epar´e). S’il ne trouve pas de mot int´eressant, Cam´elia n’a rien `a dire d’int´eressant non plus : elle se contente de relancer la conversation, en imprimant par exemple «Parlez-moi un peu de vous ». Tout le sel de la conversation avec Cam´elia vient de l’utilisateur. C’est lui qui attache un sens pr´ecis aux phrases creuses de Cam´elia. L’art du programmeur ne consiste ici qu’`a faire dire au programme les phrases les plus creuses et les plus ambigu¨es possible, pour laisser l’utilisateur interpr ´eter lui-mˆeme. Pour impl´ementer Cam´elia, nous aurons besoin de fonctions nouvelles sur les listes et les chaˆınes de caract`eres. D’autre part nous aurons besoin aussi d’introduire le m´ecanisme d’exceptions de Caml. En effet, le coeur du programme consiste `a essayer 126 Le docteur successivement les diff´erentes strat´egies possibles, jusqu’`a ce que l’une d’entre elles r´eussisse. Nous verrons que c’est justement un emploi typique des exceptions. 7.2 Les exceptions Erreurs et rattrapage d’erreurs Dans les langages fonctionnels, toute fonction qui ne boucle pas ind´efiniment doit rendre une valeur, quel que soit son argument. Malheureusement certaines fonctions, bien que n´ecessairement d´efinies pour toute valeur de leur type argument, ne peuvent pas retourner de valeur sens´ee pour tous les arguments possibles. Consid´erez par exemple la division entre nombres entiers : que doit-elle faire lorsqu’on tente de diviser par 0 ? Le probl`eme se pose aussi pour les donn´ees structur´ees : consid´erez la fonction t^ete qui renvoie la tˆete d’une liste. Que peut-elle faire lorsque son argument est la liste vide ? Dans de telles situations la fonction doit ´echouer, c’est-`a-dire arrˆeter les calculs et signaler une erreur. C’est ce que nous avons fait en utilisant la fonction pr´ed´efinie failwith : # failwith;; - : string -> ’a = qui envoie un message indiquant la cause de l’´echec. C’est pourquoi nous d´efinissons t^ete par : # let t^ete = function | [] -> failwith "t^ete" | x::_ -> x;; t^ete : ’a list -> ’a = Et maintenant, t^ete [] nous signale une erreur dans la fonction t^ete : # t^ete [];; Exception non rattrap´ee: Failure "t^ete" Ce m´ecanisme de d´eclenchement d’erreurs est utile, mais il se peut que nous voulions r´ecup´erer ces erreurs, parce que nous savons comment continuer les calculs apr`es une telle erreur (qui devient une erreur « attendue » du point de vue du programmeur). Par exemple, imaginons qu’on doive ajouter syst´ematiquement la tˆete d’une liste `a un compteur. Si la liste est vide, il est logique de continuer les calculs en n’ajoutant rien au compteur. Dans ce cas, l’´echec signal´e par la fonction t^ete doit ˆetre r´ecup´er´e. On utilise pour cela la construction try . . . with . . . (try signifie essayer et with avec) qui permet de calculer une expression en surveillant les exceptions que son calcul peut d´eclencher. Cette construction ob´eit `a la syntaxe suivante : try expression with filtrage. Elle signifie intuitivement : essayer de calculer la valeur de expression et si cette ´evaluation d´eclenche une erreur qui tombe dans un des cas du filtrage alors retourner la valeur correspondante de la clause s´electionn´ee par le filtrage. Par exemple, puisque l’erreur signal´ee par la fonction t^ete est Failure "t^ete", on envisagera cet ´echec dans la partie filtrage du try . . . with . . . pour renvoyer une valeur enti`ere, comme si aucune erreur n’avait ´et´e d´eclench´ee. On rattrape donc l’´echec sur la liste vide et l’on renvoie 0, par la phrase : # try (t^ete []) with Failure "t^ete" -> 0;; - : int = 0 Les exceptions 127 On ´ecrira donc la proc´edure d’incr´ementation du compteur : # let ajoute_au_compteur compteur l = compteur := !compteur + (try (t^ete l) with Failure "t^ete" -> 0);; ajoute_au_compteur : int ref -> int list -> unit = # let c = ref 0;; c : int ref = ref 0 # ajoute_au_compteur c [1]; !c;; - : int = 1 # ajoute_au_compteur c []; !c;; - : int = 1 C’est la m´ethode ´el´ementaire d’utilisation des exceptions de Caml. Nous d´ecrivons maintenant le m´ecanisme dans toute sa g´en´eralit´e. Valeurs exceptionnelles Le trait distinctif du traitement d’erreurs en Caml, et ce qui en fait la g´en´eralit´e, est le statut des erreurs : ce sont des valeurs `a part enti`ere du langage. Elles appartiennent `a un type pr´ed´efini exn et on les appelle « valeurs exceptionnelles ». On les manipule donc comme toutes les autres valeurs. Par exemple, l’´echec signal´e par la fonction t^ete est la valeur exceptionnelle Failure "t^ete" : # let ´echec_de_t^ete = Failure "t^ete";; ´echec_de_t^ete : exn = Failure "t^ete" Remarquez que Failure n’est rien d’autre qu’un constructeur de valeurs du type exn. La propri´et´e caract´eristique des valeurs exceptionnelles est ´evidemment qu’on peut les d´eclencher (on dit aussi les lever, par analogie avec la locution « lever une perdrix »). Pour lever une valeur exceptionnelle on utilise la fonction pr´ed´efinie raise (en anglais to raise signifie « lever ») : # raise;; - : exn -> ’a = # raise ´echec_de_t^ete;; Exception non rattrap´ee: Failure "t^ete" La primitive raise est une fonction «magique », car elle n’est pas d´efinissable dans le langage. Elle interrompt imm´ediatement les calculs en cours pour d´eclencher le signal (lever la valeur exceptionnelle) qu’elle a re¸cu en argument. C’est ce qui explique qu’un appel `a la fonction raise puisse intervenir dans n’importe quel contexte avec n’importe quel type : les calculs ne seront de toute fa¸con jamais effectu´es lorsqu’on ´evaluera l’appel `a raise, le contexte peut donc faire toutes les hypoth`eses qu’il d´esire sur la valeur renvoy´ee par raise. Par exemple : # 1 + (raise ´echec_de_t^ete);; Exception non rattrap´ee: Failure "t^ete" # "Bonjour" ^ (raise ´echec_de_t^ete);; Exception non rattrap´ee: Failure "t^ete" Bien entendu, les phrases essentiellement mal typ´ees, o`u raise apparaˆıt dans un contexte lui-mˆeme mal typ´e, sont toujours rejet´ees : 128 Le docteur # 1 + (raise ´echec_de_t^ete) ^ "Bonjour";; Entr´ee interactive: >1 + (raise ´echec_de_t^ete) ^ "Bonjour";; >^^^^^^^^^^^^^^^^^^^^^^^^^ Cette expression est de type int, mais est utilis´ee avec le type string. La construction try ... with On peut donc consid´erer les valeurs exceptionnelles comme des signaux qu’on envoie `a l’aide de la fonction raise et qu’on re¸coit avec la construction try . . . with . . . La s´emantique de try e with filtrage est de retourner la valeur de e si e s’´evalue « normalement », c’est-`a-dire sans d´eclenchement d’exception. En revanche, si une valeur exceptionnelle est d´eclench´ee pendant l’´evaluation de e, alors cette valeur est filtr´ee avec les clauses du filtrage et comme d’habitude la partie expression de la clause s´electionn´ee est renvoy´ee. Ainsi, la partie filtrage de la construction try . . . with . . . est un filtrage parfaitement ordinaire, op´erant sur des valeurs du type exn. La seule diff´erence est qu’en cas d’´echec du filtrage, la valeur exceptionnelle qu’on n’a pas pu filtrer est propag´ee, c’est-`a-dire d´eclench´ee `a nouveau. Comparez ainsi une ´evaluation habituelle : # try t^ete [1] with Failure "t^ete" -> 0;; - : int = 1 une ´evaluation d´eclenchant une valeur exceptionnelle rattrap´ee : # try t^ete [] with Failure "t^ete" -> 0;; - : int = 0 et finalement une propagation de valeur exceptionnelle : # try t^ete [] with Failure "reste" -> 0;; Exception non rattrap´ee: Failure "t^ete" D´efinition d’exceptions De nombreuses fonctions pr´ed´efinies de Caml, quand elles ´echouent, d´eclenchent l’exception Failure avec leur nom comme argument. C’est pourquoi l’exception Failure poss`ede un « d´eclencheur » pr´ed´efini, la fonction failwith. Nous pouvons maintenant ´ecrire sa d´efinition : # let failwith s = raise (Failure s);; failwith : string -> ’a = Si les exceptions pr´ed´efinies ne vous satisfont pas, parce que vous souhaitez par exemple que votre valeur exceptionnelle transporte autre chose qu’une chaˆıne de caract` eres, vous pouvez d´efinir une nouvelle exception. En effet, le type exn est un type somme (il y a plusieurs exceptions diff´erentes ; c’est donc un type « ou »), mais d’un genre tr`es particulier : sa d´efinition n’est jamais achev´ee. C’est pourquoi il est possible `a tout moment de lui ajouter de nouveaux constructeurs, soit constants soit fonctionnels. Pour d´efinir un nouveau constructeur du type exn, donc une nouvelle exception, on utilise le mot-cl´e exception suivi d’une d´efinition de constructeur de type somme. Pour d´efinir la nouvelle exception constante Stop, on ´ecrira donc simplement : Les exceptions 129 # exception Stop;; L’exception Stop est d´efinie. La d´efinition d’une exception fonctionnelle comportera une partie « of type » qui pr´ecise le type de l’argument de l’exception. # exception Erreur_fatale of string;; L’exception Erreur_fatale est d´efinie. # raise (Erreur_fatale "Cas impr´evu dans le compilateur");; Exception non rattrap´ee: Erreur_fatale "Cas impr´evu dans le compilateur" Voici la description pr´ecise des d´efinitions d’exception `a l’aide de diagrammes syntaxiques : D´efinition d’exceptions ::= exception d´efinition-de-constructeur (and d´efinition-de-constructeur)∗ d´efinition-de-constructeur ::= identificateur | identificateur of type Les exceptions comme moyen de calcul Les exceptions ne servent pas seulement `a g´erer les erreurs : elles sont aussi utilis´ees pour calculer. Dans ce cas, la valeur exceptionnelle transporte un r´esultat, ou bien signale un ´ev´enement attendu. `A titre d´emonstratif, nous d´efinissons la fonction caract`ere_dans_cha^ıne, qui teste l’appartenance d’un caract`ere `a une chaˆıne et dont nous avons besoin pour impl´ementer Cam´elia. On pourrait ´evidemment ´ecrire cette fonction `a l’aide d’une fonction r´ecursive locale : # let caract`ere_dans_cha^ıne cha^ıne car = let rec car_dans_cha^ıne i = i < string_length cha^ıne && (cha^ıne.[i] = car || car_dans_cha^ıne (i + 1)) in car_dans_cha^ıne 0;; caract`ere_dans_cha^ıne : string -> char -> bool = Cependant, cette fonction r´ecursive code ´evidemment une boucle ; nous pr´ef´erons donc l’´ecrire avec une boucle. On parcourt donc la chaˆıne argument `a l’aide d’une boucle for en recherchant le caract`ere donn´e. Cependant, que faire si le caract`ere est trouv´e ? Il faut arrˆeter la boucle et signaler sa pr´esence. Ce comportement revient `a d´eclencher une exception. Nous d´efinissons donc l’exception Trouv´e. Et nous surveillons la boucle de recherche : si l’exception est d´eclench´ee, la fonction renvoie true. En revanche, si la boucle se termine normalement, c’est que le caract`ere n’´etait pas dans la chaˆıne ; dans ce cas, on renvoie false en s´equence. # exception Trouv´e;; L’exception Trouv´e est d´efinie. # let caract`ere_dans_cha^ıne cha^ıne car = try for i = 0 to string_length cha^ıne - 1 do if cha^ıne.[i] = car then raise Trouv´e done; false 130 Le docteur with Trouv´e -> true;; caract`ere_dans_cha^ıne : string -> char -> bool = Ici le d´eclenchement de l’exception n’est pas un cas d’erreur, mais plutˆot un ´ev´enement heureux : on a d´etect´e la pr´esence du caract`ere dans la chaˆıne. On ne peut pas dire non plus que ce soit vraiment un ´ev´enement exceptionnel, une « exception » au calcul normal : c’est un signal attendu, tout simplement. Sans le m´ecanisme des exceptions la fonction pr´ec´edente devrait ˆetre ´ecrite avec une r´ef´erence initialis´ee `a false en d´ebut de boucle et mise `a true lorsqu’on rencontre le caract`ere. # let car_dans_cha^ıne cha^ıne car = let trouv´e = ref false in for i = 0 to string_length cha^ıne - 1 do if cha^ıne.[i] = car then trouv´e := true done; !trouv´e;; car_dans_cha^ıne : string -> char -> bool = Cette version est un peu moins efficace, puisque le parcours de la chaˆıne est toujours effectu´e compl`etement, alors qu’il est inutile de le continuer d`es qu’on a d´etect´e la pr´esence du caract`ere. Cet argument d’efficacit´e est minime : le choix entre les deux versions est essentiellement une affaire de goˆut personnel, de style et d’exp´erience. Nous pr´ef´erons la version avec exception, car elle se g´en´eralise plus facilement `a plusieurs ´ev´enements attendus dans la boucle. Au surplus, la boucle s’arrˆete instantan´ement quand l’´ev´enement arrive et c’est tr`es souvent un comportement algorithmiquement n´ecessaire du programme. 7.3 Fonctions de recherche dans les listes Les r´eponses toutes faites de Cam´elia sont stock´ees dans des listes d’une forme particuli`ere, les listes d’association, qui associent des r´eponses `a certains mots de la phrase du patient. Appartenance d’un ´el´ement `a une liste Nous commen¸cons par ´ecrire la fonction membre, qui d´etermine si son premier argument est ´el´ement d’une liste donn´ee en second argument. Travaillant sur des listes, la fonction membre doit par cons´equent envisager les deux cas possibles de listes ; d’o`u le squelette de fonction : let membre e = function | [] -> ... | x :: reste -> ... ;; Le cas de la liste vide est simple : l’´el´ement `a rechercher n’apparaˆıt certainement pas dans la liste. let membre e = function | [] -> false | x :: reste -> ... ;; Fonctions de recherche dans les listes 131 Dans le cas g´en´eral, il faut tester si e est ´egal `a la tˆete de la liste (x), sinon interroger r´ecursivement le reste de la liste. On obtient donc : # let rec membre elem = function | [] -> false | x :: reste -> x = elem || membre elem reste;; membre : ’a -> ’a list -> bool = Listes d’association Les listes d’association sont simplement des listes de paires o`u le premier ´el´ement de la paire est une cl´e et le second la valeur associ´ee `a la cl´e. (Pour les tenants de Bourbaki, c’est le graphe d’une fonction donn´ee en extension.) Par exemple, consid´erez une liste d’adresses. On associe au nom d’une personne son adresse : # let adresses = [("Pierre Caml","Domaine de Voluceau, 78153 Le Chesnay Cedex"); ("Xavier Light","45 rue d’Ulm, 75005 Paris")];; adresses : (string * string) list = ["Pierre Caml", "Domaine de Voluceau, 78153 Le Chesnay Cedex"; "Xavier Light", "45 rue d’Ulm, 75005 Paris"] Dans notre programme, une liste d’association donnera les r´eponses possibles associ´ees aux phrases tr`es simples. Voici un extrait de la liste d’association des phrases simples : let r´eponses_aux_phrases_simples = [([], [|"Voulez-vous changer de sujet?"; "Continuez"|]); (["et"; "alors"], [|"Alors expliquez-moi"; "Ne soyez pas si agressif"|]); (["non"], [|"C’est vite dit"; "Pourriez-vous pr´eciser ?"|]); (["oui"], [|"C’est un peu rapide"; "Donnez-moi plus de d´etails"|])];; Cette liste associe `a la liste vide (correspondant `a une r´eponse vide) le tableau de r´eponses possibles [|"Voulez-vous changer de sujet?"; "Continuez"|]. Elle associe `a la phrase r´eduite `a et alors? les r´eponses [|"Alors expliquez-moi"; "Ne soyez pas si agressif"|]. Il nous faut donc retrouver la valeur associ´ee `a une phrase dans cette liste d’association. La fonction g´en´erale de recherche dans une liste d’association s’´ecrit tr`es simplement : on teste si la cl´e a ´et´e rencontr´ee, auquel cas on renvoie la valeur associ´ee ; sinon on cherche dans le reste de la liste. Si la liste est ´epuis´ee, il n’y a pas d’espoir de trouver la valeur associ´ee et l’on d´eclenche l’exception constante Pas_trouv´e. # exception Pas_trouv´e;; L’exception Pas_trouv´e est d´efinie. # let rec associ´e_de x = function | [] -> raise Pas_trouv´e | (cl´e, valeur) :: l -> if x = cl´e then valeur else associ´e_de x l;; associ´e_de : ’a -> (’a * ’b) list -> ’b = 132 Le docteur # associ´e_de "Pierre Caml" adresses;; - : string = "Domaine de Voluceau, 78153 Le Chesnay Cedex" # associ´e_de "Xavier Light" adresses;; - : string = "45 rue d’Ulm, 75005 Paris" # associ´e_de "G´erard Coq" adresses;; Exception non rattrap´ee: Pas_trouv´e Cette fonction est pr´ed´efinie en Caml sous le nom de assoc. En cas d’´echec, elle d´eclenche toujours une exception. Dans le syst`eme Caml Light c’est l’exception Not_found (signifiant « pas trouv´e » en anglais). C’est souvent l’exception d´eclench´ee par les fonctions de recherche de la biblioth`eque Caml Light. Des listes d’association multi-cl´es Pour les besoins de notre programme nous devons g´erer des listes d’association plus compliqu´ees que des listes (cl´e, valeur). En effet, nous consid´erons que certains mots « int´eressants » sont synonymes et donc que leurs r´eponses associ´ees sont les mˆemes. Par exemple, ordinateur et machine appellent les mˆemes r´eponses. Le pluriel d’un mot est souvent trait´e comme le mot lui-mˆeme, par exemple ami et amis. Dans ces listes, une valeur n’est donc plus associ´ee `a une seule cl´e, mais `a une liste de cl´es ´equivalentes. Nous ´ecrivons donc la fonction qui teste si une cl´e donn´ee fait partie de la liste de cl´es et retourne en ce cas la valeur associ´ee : # let rec associ´e_dans_liste cl´e = function | [] -> raise Pas_trouv´e | (liste_de_cl´es, valeur) :: reste -> if membre cl´e liste_de_cl´es then valeur else associ´e_dans_liste cl´e reste;; associ´e_dans_liste : ’a -> (’a list * ’b) list -> ’b = De la mˆeme mani`ere, nous avons besoin de chercher, parmi une liste de cl´es la valeur associ´ee `a la premi`ere cl´e qui figure dans les cl´es d’une liste d’association (listes de cl´es, valeur). Nous parcourons donc la liste de cl´es argument en cherchant l’associ´e de la premi`ere cl´e rencontr´ee. S’il n’y a pas d’associ´e `a cette cl´e, nous cherchons, parmi le reste de la liste de cl´es argument, la premi`ere cl´e qui ait un associ´e dans la liste d’association. Remarquez au passage que le caract`ere ’ est autoris´e dans les noms d’identificateur en Caml. # let rec associ´e_d’un_´el´ement_de liste_de_cl´es liste_d’association = match liste_de_cl´es with | [] -> raise Pas_trouv´e | cl´e :: reste -> try associ´e_dans_liste cl´e liste_d’association with Pas_trouv´e -> associ´e_d’un_´el´ement_de reste liste_d’association;; associ´e_d’un_´el´ement_de : ’a list -> (’a list * ’b) list -> ’b = Traitements de chaˆınes de caract`eres 133 7.4 Traitements de chaˆınes de caract`eres Notre programme va effectuer une certaine normalisation de l’entr´ee de l’utilisateur : passer syst´ematiquement toute la phrase en minuscules et ˆoter les signes de ponctuation et accents ´eventuels, par exemple. Les fonctions correspondantes illustrent le traitement de chaˆınes de caract`eres en Caml. Passage en minuscules En machine, les caract`eres sont ´evidemment enregistr´es comme des nombres. Le codage utilis´e en Caml s’appelle le code ASCII. Il suffit de le faire imprimer par Caml pour comprendre comment sont rang´es les caract`eres de l’alphabet (remarquez aussi les chiffres) : # for i = 32 to 126 do if i < 100 then print_string " "; print_int i; print_string " "; print_char (char_of_int i); print_string " "; if i mod 8 = 7 then print_newline () done; print_newline ();; 32 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ’ 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ 96 ‘ 97 a 98 b 99 c 100 d 101 e 102 f 103 g 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ - : unit = () Le passage en minuscule revient donc `a un simple calcul sur le code ASCII du caract` ere : si le caract`ere est une majuscule, on lui ajoute 32 pour obtenir la minuscule correspondante. # let minuscule_de car = if int_of_char car >= 65 && int_of_char car <= 90 then char_of_int (int_of_char car + 32) else car;; minuscule_de : char -> char = Pour passer une chaˆıne de caract`eres tout enti`ere en minuscules, il suffit d’it´erer la fonction pr´ec´edente. # let minuscules cha^ıne = let cha^ıne_en_minuscules = create_string (string_length cha^ıne) in for i = 0 to string_length cha^ıne - 1 do cha^ıne_en_minuscules.[i] <- minuscule_de cha^ıne.[i] done; 134 Le docteur cha^ıne_en_minuscules;; minuscules : string -> string = Nous avons ´egalement besoin d’extraire une sous-chaˆıne d’une chaˆıne. La souscha ˆıne est rep´er´ee par ses indices de d´ebut et de fin. On utilise la fonction pr´ed´efinie sub_string qui calcule une sous-chaˆıne partant d’un indice donn´e et d’une longueur donn´ee : # sub_string "Caml" 0 3;; - : string = "Cam" # let sous_cha^ıne s d´epart fin = sub_string s d´epart (fin - d´epart + 1);; sous_cha^ıne : string -> int -> int -> string = Suppression des accents La simplification d’un mot consiste `a supprimer les accents, que l’on consid`ere comme non significatifs. On admet ´egalement que l’utilisateur ait pu taper e^ pour ^e ou e’ pour ´e. Enfin, on supprime les articles ´elid´es qui apparaissent ´eventuellement au d´ebut du mot. Par exemple, l’air devient air. Plutˆot que de «mettre en dur » ces conventions dans le code de la fonction de simplification, nous ´ecrivons une fonction g´en´erale, param´etr´ee par une table de simplifications, qui n’est autre qu’une liste d’association entre chaˆınes. # let simplifications = [("`a","a"); ("¸c","c"); ("´e","e"); ("`e","e"); ("^e","e"); ("`u","u"); ("a‘","a"); ("e’","e"); ("e‘", "e"); ("e^","e"); ("u‘","u"); ("qu’", ""); ("l’", ""); ("d’", "")];; La fonction de simplification consiste `a recopier le mot argument dans une nouvelle chaˆıne. Les caract`eres sont recopi´es un par un, sauf si le caract`ere courant et les caract` eres suivants forment une des chaˆınes `a simplifier, auquel cas on les remplace par la chaˆıne associ´ee dans la table de simplifications. # let simplifie_mot mot = let nouveau_mot = create_string (string_length mot) in let i = ref 0 and j = ref 0 in let rec cherche_traduction = function | [] -> raise Pas_trouv´e | (original, traduction) :: reste -> let longueur = string_length original in if !i + longueur <= string_length mot && sub_string mot !i longueur = original then (longueur, traduction) else cherche_traduction reste in while !i < string_length mot do try let (longueur, traduction) = cherche_traduction simplifications in blit_string traduction 0 nouveau_mot !j (string_length traduction); i := !i + longueur; Cam´elia 135 j := !j + string_length traduction with Pas_trouv´e -> nouveau_mot.[!j] <- mot.[!i]; i := !i + 1; j := !j + 1 done; sub_string nouveau_mot 0 !j;; simplifie_mot : string -> string = Division en mots La division d’une chaˆıne de caract`eres en mots est une autre op´eration d´elicate. Elle consiste `a parcourir la chaˆıne (`a l’envers) `a la recherche des s´eparateurs. D`es qu’un s´eparateur est trouv´e, on extrait un mot qu’on ajoute `a la liste de mots mots. On maintient un compteur j qui indique le dernier caract`ere du mot courant, tandis que le compteur de boucle i sert `a en rep´erer le d´ebut. Notez que le caract`ere « fin de ligne » est ´ecrit \n. `A l’int´erieur d’une chaˆıne de caract`eres, la notation \n repr´esente aussi un retour `a la ligne. # let divise_en_mots cha^ıne = let mots = ref [] in let j = ref (string_length cha^ıne - 1) in let ajoute_mot i j = if i <= j then mots := simplifie_mot (sous_cha^ıne cha^ıne i j) :: !mots in for i = string_length cha^ıne - 1 downto 0 do match cha^ıne.[i] with | (‘ ‘ | ‘\n‘ | ‘.‘ | ‘,‘ | ‘;‘ | ‘-‘ | ‘!‘ | ‘?‘) -> ajoute_mot (i + 1) !j; j := i - 1 | _ -> () done; ajoute_mot 0 !j; (* extraction du dernier mot *) !mots;; divise_en_mots : string -> string list = Nous rencontrons un trait nouveau du langage : les barres verticales `a l’int´erieur des filtres, comme dans ‘ ‘ | ‘\n‘ | . . . Ces filtres sont des filtres `a plusieurs cas que l’on appelle les filtres « ou ». Ils filtrent l’union des cas filtr´es par leurs composants. C’est une simple facilit´e syntaxique, qui ´evite de recopier plusieurs fois le corps de la clause. Par exemple, la clause (1 | 2) -> true est ´equivalente aux deux clauses 1 -> true | 2 -> true. 7.5 Cam´elia La base de donn´ees Le principe de Cam´elia est donc d’utiliser un ensemble de phrases ´ecrites pour elle par le programmeur. Le bon choix de ces phrases intervient beaucoup dans la qualit´e de l’´echange. Nous donnons donc ici ce que nous appelons pompeusement la « base de 136 Le docteur donn´ees » de Cam´elia, qui n’est autre qu’un ensemble de listes d’association simples ou multi-cl´es. La base de donn´ees comprend les listes suivantes : • salutations : le mot de la fin de Cam´elia, typiquement «Ce sera long et difficile, revenez me voir souvent . . . ». • relances : les phrases utilis´ees quand le programme ne sait plus quoi dire, typiquement «Parlez-moi encore de vous » ou « ˆEtes-vous mari´e ? ». • r´eponses_types : ce que le programme r´epond quand il d´etecte un caract`ere « ? » et rien de plus int´eressant dans la phrase. Typiquement «C’est moi qui pose les questions ». • r´eponses_aux_phrases_simples : pour r´epondre aux phrases `a l’emporte-pi`ece comme «Et alors ? » ou «Oui ». R´eponse typique : «Alors, expliquez-moi » ou «Donnez-moi plus de d´etails ». • r´eponses_aux_petits_mots : quand le programme n’a rien de mieux `a faire, il cherche un mot tr`es souvent employ´e en fran¸cais qui lui permette de faire une r´eponse sens´ee, sans rien comprendre. Exemple typique : si la phrase contient le mot « jamais » le programme peut r´epondre « Jamais me semble un peu fort, non ? ». • r´eponses_aux_mots_int´eressants : le programme surveille si le patient emploie des mots « psychanalytiques », comme p`ere ou famille, et r´eagit alors en cons´equence. R´eponses typiques : «Racontez-moi vos probl`emes » quand le mot «malheureux » est d´etect´e. let salutations = [|"Ce sera long et difficile, revenez me voir \ souvent ..."; "Votre cas n’est pas simple, et m^eme assez \ inqui´etant ... A bient^ot?"; "Diagnostic simple: sans conteste vous ^etes \ parano¨ıaque."; "Avec une probabilit´e de 92.37234%: \ perversion polymorphe."; "Vous souffrez d’une schizophr´enie en rapide \ ´evolution, DANGER"; "D’apr`es mes calculs, votre sant´e mentale est \ compromise."; "Mon ultime conseil: il ne faut pas rester \ comme cela, soignez-vous!"|];; let relances = [| "Parlez-moi un peu de vous"; "^Etes-vous mari´e?"; "Avez-vous des enfants?"; "Parlons de votre entourage"; "Aimez-vous la vie?"; "Aimez-vous ce moyen de communiquer?"; "Parlons de votre famille"; "Parlez-moi encore de vous"; "Que pensez-vous des ordinateurs?"; "Que pensez-vous de Linux?"; "Que pensez-vous de Caml?"; "De quoi parlerons-nous maintenant?"; "Avez-vous beaucoup d’amis?"; "Avez-vous de graves probl`emes?"; "Parlez-moi de vos probl`emes"; "Faites-vous des r^eves ´etranges?"; "Faites-vous souvent des cauchemars?"; "Que pensez-vous de l’amour?"; "Que pensez-vous de la sexualit´e?"; "Quels sont vos violons d’Ingres?"; "Qu’est-ce qui vous int´eresse dans la vie?"; "Que pensez-vous de la vie en g´en´eral?"|];; let r´eponses_types = [| "C’est moi qui pose les questions"; "Je ne suis pas l`a pour r´epondre `a vos \ questions"; "Question tr`es int´eressante, mais qu’en \ pensez-vous?"; "Quelle question!"; "Pourquoi me posez-vous cette question?"; "Vous le savez tr`es bien"; "La r´eponse est sans importance"; "Vous le dire ne vous apporterait rien"; "Un psychanalyste n’a pas le droit de \ r´epondre `a ce genre de questions"; "Je n’ai pas le droit de vous r´epondre"; "Il m’est interdit de vous le dire"; "Vous ne comprendriez pas"; "Permettez-moi de ne pas r´epondre"; "Laissez-moi r´efl´echir. Pouvez-vous \ reformuler la question?"; "Je ne suis pas certaine de bien comprendre \ la question"; "Je ne sais pas"; "Cherchez un peu"; "C’est ´evident pour tout le monde, sauf pour \ Cam´elia 137 vous; r´efl´echissez!"; "C’est `a vous de trouver la r´eponse"; "Cherchez bien au fond de vous-m^eme, vous le \ savez en fait"|];; let r´eponses_aux_phrases_simples = [([], [|"Voulez-vous changer de sujet?"; "Continuez"; "Continuez, vous m’int´eressez"; "Je vous ´ecoute"; "Vous n’avez rien d’autre `a dire?"; "Continuez, je vous prie"; "C’est tout ce que vous avez `a dire?"; "M’avez-vous tout dit l`a-dessus?"; "Je n’en sais pas encore assez sur vous; \ continuez"|]); (["quoi"], [|"Excusez-moi je pensais `a autre chose, \ continuons"; "R´efl´echissez"; "Changeons de sujet, s’il vous pla^ıt"; "Je me comprends"; "Il me semblait pourtant avoir ´et´e claire"; "La communication est difficile, non?"; "Ah les hommes! Ils ne comprennent rien!"; "Cessez de poser des questions"; "N’auriez-vous pas des probl`emes `a me \ comprendre?"|]); (["non"], [|"C’est vite dit"; "Pourriez-vous pr´eciser?"; "Je note: c’est non"; "Mais encore?"; "La r´eponse n’est pas si simple, non?"; "Vous ^etes vraiment tr`es s^ur de vous"; "Ne vous arrive-t-il pas de douter de \ vous-m^eme?"; "Ne r´epondez pas toujours oui ou non"; "Syndr^ome du yes/no. Expliquez-vous, que \ diable!"; "Au moins vous ne souffrez pas de diarrh´ee \ verbale"; "Comment pouvez-vous ^etre si s^ur de \ vous?"|]); (["si"], [|"Si b´emol?"; "D’accord, d’accord"; "Mouais, je m’en doutais un peu, \ figurez-vous"; "Expliquez-vous, ‘‘si’’ ne me suffit pas"; "R´eponse trop laconique"; "Syndr^ome du si"; "Vous n’^etes pas bavard vous au moins"|]); (["oui"], [|"C’est un peu rapide"; "Donnez-moi plus de d´etails"; "Vous pourriez pr´eciser?"; "Je voudrais comprendre pourquoi"; "La r´eponse n’est pas si simple, non?"; "C’est franc et massif au moins"; "C¸a ne m’en dit pas vraiment plus, \ expliquez-moi pourquoi."; "Vous ^etes s^ur?"; "Soyez moins bref: d´eveloppez"; "Plus laconique tu meurs"; "Si vous ne m’expliquez pas mieux, comment \ vous comprendre?"; "Ne r´epondez pas toujours oui ou non"; "Dont acte"; "Et pour quelles raisons?"|]); (["et"; "alors"], [|"Alors, expliquez-moi"; "Ne soyez pas si agressif"; "Alors j’aimerais avoir plus d’informations \ l`a-dessus"; "Zorro est arriv´e"; "Et alors, et alors, expliquez-vous!"; "C’´etait un test pour savoir si vous \ suiviez"|]); (["encore"], [|"On peut changer de sujet, si vous voulez?"; "Il faut bien crever l’abc`es!"; "Les choses importantes doivent ^etre \ dites!"; "Il faut savoir affronter les probl`emes"; "Je suis plus t^etue que vous!"; "Pensez-vous que je radote?"; "Dites tout de suite que je suis \ g^ateuse!"|]) ];; let r´eponses_aux_petits_mots = [(["nest"], [|"Pas du tout?"; "Vraiment pas?"; "Pourquoi pas?"|]); (["jamais"], [|"Ne dites jamais ‘‘jamais’’"; "Jamais me semble un peu fort, non?"; "Jamais?"|]); (["non"], [|"En ^etes vous s^ur?"; "Pourquoi pas?"; "Que diriez-vous dans le cas contraire?"; "C’est une opinion d´efendable"; "Je saurai au moins votre opinion \ l`a-dessus"|]); (["rien"], [|"Rien du tout?"; "Pourquoi pas?"; "Que diriez-vous dans le cas contraire?"; "Voil`a qui est franc"; "Au moins c’est clair"; "M^eme pas un petit peu?"; "Rien est un peu exag´er´e, non?"|]); (["pourquoi"], [| "Parce que"; "Je ne r´eponds pas aux questions des \ malades"; "Si vous le savez pas, ce n’est pas `a moi \ de vous l’apprendre"; "Personne ne peut r´epondre `a cette \ question"; "Pensez-vous qu’une machine peut r´epondre \ `a ¸ca?"; "Ce serait trop long `a expliquer"; "Je sais bien pourquoi, mais vous ne \ comprendriez pas"; "C’est difficile `a dire"|]); (["aucun"], [|"Vraiment aucun?"; 138 Le docteur "Pas le moindre?"; "Le regrettez-vous?"; "C’est un fait nouveau pour moi"|]); (["pas"], [|"C¸a me semble un peu n´egatif"; "Vraiment?"; "Pourquoi cela?"; "Je ne m’en serais pas dout´ee"; "Difficile"; "J’ai l’habitude d’entendre ¸ca"; "^Etes vous troubl´e `a ce point?"; "Vous ne devriez pas parler ainsi"|]); (["sait"; "sais"; "savoir"], [|"Le savoir est une denr´ee rare"; "^Etes-vous certain de le savoir?"; "Ne subsiste-t-il pas de doute?"; "Je ne pourrais pas en dire autant"; "Difficile `a admettre"; "En ^etes-vous si s^ur?"|]); (["oui"], [|"En ^etes-vous certain?"; "Vous ^etes vraiment s^ur de vous"; "C¸a ne me semblait pas ´evident"; "Pourtant j’aurais cru le contraire"; "C’est int´eressant, continuez"; "Quelle affirmation sans d´etours"; "Tr`es bien"; "Quel aveu!"; "Bon"|]); (["quoi";"comment"], [|"C’est `a vous de me le dire"; "Difficile `a dire"; "R´efl´echissez, vous comprendrez"; "La r´eponse est en vous"|]); (["merci";"remercie"], [|"Ne me remerciez pas"; "Je suis l`a pour vous aider"; "Allez allez, continuez"; "C’est tout naturel"; "C’´etait vraiment facile"|]) ];; let r´eponses_aux_mots_int´eressants = [(["peur";"peurs"], [|"Parlez-moi de vos frayeurs"; "Avez-vous souvent peur?"; "Avez-vous des peurs inexpliqu´ees, des \ cauchemars?"|]); (["mort"; "morte"; "morts"], [|"Je vous plains beaucoup"; "La mort est un sujet tr`es grave"; "Il faut essayer de prendre le dessus"; "Il faut pourtant quelquefois affronter la \ mort"; "C’est malheureux"; "Essayez de ne plus y penser"|]); (["malheureux"; "malheureuse"; "probleme"; "problemes"], [|"Racontez-moi vos probl`emes"; "Quels malheurs sont les v^otres?"; "Avez-vous vraiment des raisons de vous \ plaindre?"; "Le bonheur existe aussi vous savez."|]); (["malheur"; "malheurs"], [|"Malheur est peut-^etre exag´er´e, non?"; "Le malheur est une notion relative. \ Qu’entendez-vous par malheur?"; "Bonheur, malheur, je n’entends parler que \ de ¸ca. Continuez."|]); (["ennui"; "ennuies"; "ennuyez"], [|"L’ennui, ¸ca d´epend de vous"; "Est-ce que je vous ennuie?"; "Je le regrette pour vous"; "C’est dommage pour vous"|]); (["ennuis"], [|"Les ennuis sont souvent passagers"; "Tout ne peut pas ^etre rose, n’est-ce pas?"; "Quelle tristesse, n’est-ce pas?"; "Est-ce vraiment tr`es grave?"|]); (["ordinatuer"], [| "Vous voulez dire ordinateur, je \ suppose"|]); (["ordinateur"; "ordinateurs"; "machine"; \ "machines"], [|"Connaissez-vous bien l’informatique?"; "Changeons de sujet, celui-l`a ne \ m’int´eresse pas"; "Ah les machines!"; "Les machines c’est si b^ete!"; "Je connais bien les ordinateurs, et \ j’´evite de les fr´equenter!"; "Je n’ai pas d’avis sur les machines en \ g´en´eral"; "Vous savez, je suis une machine moi-m^eme \ ..."|]); (["informatique"; "informaticien"; \ "informaticiens"], [|"Quel beau m´etier de s’occuper des \ machines"; "Ah l’informatique!"; "L’informatique est un dur m´etier"; "C’est difficile l’informatique, non?"; "Aimez-vous vraiment l’informatique?"; "Vous n’aimez pas follement l’informatique, \ m’a-t’on dit"|]); (["famille"], [|"Avez-vous des fr`eres et soeurs?"; "Parlez-moi de votre p`ere"; "Parlez-moi de votre m`ere"; "Voil`a qui m’int´eresse ´enorm´ement"; "Dites-m’en plus sur votre famille"; "La famille c’est souvent compliqu´e"|]); (["pere"], [|"Ressemblez-vous `a votre p`ere?"; "Parlez-moi encore de votre p`ere"; "Et votre m`ere?"; "Votre p`ere?"|]); (["mere"], [|"Ressemblez-vous `a votre m`ere ou `a votre \ p`ere?"; "Parlez-moi de votre m`ere"; "Parlez-moi encore de votre m`ere"; "Et votre p`ere?"; "Votre m`ere?"|]); (["ami"; "amis"; "amie"; "amies"; "copains"; \ "copines"], [|"Avez-vous beaucoup d’amis?"; "Comment vous ^etes-vous connus?"; "Comment cela se passe-t-il avec vos amis?"; "Avez-vous de fr´equentes disputes avec vos \ amis?"; Cam´elia 139 "Des amies?"; "Des petites amies?"; "Des petits amis?"; "Depuis combien de temps vous \ connaissez-vous?"|]); (["deteste"; "hais"], [|"Est-ce raisonnable de d´etester `a ce \ point?"; "Le mot n’est-il pas un peu fort?"; "Mod´erez un peu vos sentiments"|]); (["mari"], [|"^Etes-vous depuis longtemps ensemble?"; "Comment l’avez-vous rencontr´e?"; "Pensez-vous qu’il faille ^etre fid`ele `a son \ mari?"|]); (["amour"], [|"Et l’amour fou, qu’en pensez-vous?"; "C’est compliqu´e l’amour, non?"; "L’amour, l’amour, le connaissez-vous \ vraiment?"; "Avez-vous d´ej`a connu l’amour?"; "Connaissez-vous le grand amour?"; "L’amour, comment l’avez-vous \ rencontr´e?"|]); (["argent"], [|"Faute d’argent, c’est douleur sans \ pareille"; "Avez-vous des probl`emes d’argent?"; "L’argent a beaucoup de connotations, \ continuez sur le sujet"; "Aimez-vous beaucoup l’argent?"; "Avez-vous peur de manquer d’argent?"|]); (["caml"], [|"Vous voulez dire les cigarettes Camel?"; "J’ai entendu parler de ce remarquable \ langage Caml"; "Tout ce que vous allez dire pourra ^etre \ retenu contre vous"; "Sans Caml je ne serais pas l`a; je refuse \ donc d’en parler"; "A mon avis, Caml est sans ´egal"; "Ce langage Caml est clairement en avance \ sur nombre de ses successeurs!"; "Caml est puissant, et quelle belle \ syntaxe, hein?"; "Caml, c’est vraiment facile"; "Caml, c’est un langage de la sixi`eme \ g´en´eration, non?"; "C’est vrai que si Caml n’existait pas, il \ faudrait l’inventer d’urgence!"; "Je suis cat´egorique: Caml est un langage \ tr`es simple!"; "En Caml, c’est trop simple: les programmes \ marchent toujours!"; "Un tel langage, quelle aubaine pour les \ humains!"; "Caml, ¸ca c’est du langage!" |] ); (["sml"], [|"Pas de provocation s’il vous pla^ıt"; "Ne me parlez pas des mammouths"; "SML, dites-vous?"; "Jamais entendu parler de SML, c’est \ quoi?"; "Faudrait savoir est-ce ML ou pas?"|]); (["langage"; "langages"], [|"Vous voulez dire langage de \ programmation?"; "Je ne connais que le langage Caml"; "Connaissez-vous bien le langage Caml?"; "Hors de Caml, point de salut, non?"; "A mon avis, Caml est sans ´egal"; "Oui, c’est puissant, mais quelle syntaxe!"; "Et les probl`emes de syntaxe?" |] ); (["programme"; "programmes"], [|"Vous parlez de programmes d’ordinateur?"; "Il y a souvent des erreurs dans vos \ programmes, non?"; "Connaissez-vous vraiment la \ programmation?"; "Vos programmes s’´ecriraient plus \ naturellement en Caml"; "Vos programmes s’´ecriraient plus \ simplement en Caml"; "A mon avis, la programmation c’est facile, \ non?"; "Avez-vous des probl`emes avec vos \ programmes?" |] ); (["chameaux"; "chameau"], [|"Le chameau est un charmant animal d’une \ grande sobri´et´e, non?"; "Le chameau est mon animal favori, pas \ vous?"; "Certes le chameau est d’un caract`ere un \ peu difficile, mais il en est de \ charmants, n’est-ce-pas?"; "Un chameau `a deux bosses ou un \ dromadaire?"; "Qu’avez-vous de plus `a dire sur les \ chameaux?"|]); (["naime"], [|"M^eme pas un peu?"; "D´etestez-vous carr´ement?"; "Pourquoi cette r´epulsion?"; "Aimer me semble un sentiment ´etrange, pas \ vous?"; "Peut-on aimer vraiment?"; "Aimer ne pas aimer est-ce vraiment la \ question?"|]); (["aime"], [|"Beaucoup?"; "Sans aucune retenue?"; "Pourquoi cette attirance?"; "Comment expliquer ce sentiment?"; "Peut-on aimer vraiment?"; "Aimer ne pas aimer est-ce vraiment la \ question?"|]); (["sexe"], [|"Personnellement je ne suis pas concern´ee"; "C¸a para^ıt int´eressant!"; "On m’a dit que le sexe est important pour \ les humains"; "Le sexe d’accord, mais l’amour?"; "Avez-vous entendu parler du Sida?"|]); (["cauchemar"; "cauchemars"; "reve"; "reves"], 140 Le docteur [|"J’ai du mal `a comprendre; je ne r^eve \ jamais!"; "Vos activit´es nocturnes m’int´eressent. \ Continuez"; "C¸a me para^ıt bizarre!"; "Les cauchemars vous r´eveillent-ils la \ nuit?"; "Avez-vous des insomnies?"; "Faites-vous beaucoup de cauchemars?"; "Faites-vous souvent des r^eves ´etranges?"; "Que pensez-vous de l’hypnose?"|]); (["anxieux"; "anxieuse"], [|"L’anxi´et´e est une vraie maladie"; "Les anxieux ont souvent des probl`emes avec \ leur entourage. L’avez-vous remarqu´e?"; "L’anxi´et´e est une vraie souffrance, \ non?"|]); (["stupide"; "idiot"], [|"Pensez-vous que ce soit un crime d’^etre \ stupide?"; "J’ai d’excellents amis qui sont stupides \ aussi"; "La sottise est la chose du monde la mieux \ partag´ee"; "Ne soyez pas stupide non plus"; "Vous-m^eme, n’^etes-vous pas stupide \ quelquefois?"; "Ne pensez-vous pas que c’est quelquefois \ utile d’^etre stupide?"|]); (["femme"], [|"^Etes-vous depuis longtemps ensemble?"; "Comment votre rencontre s’est-elle \ pass´ee?"; "Aimez-vous cette femme?"; "Est-ce une femme ordinaire?"|]); (["mal"; "difficile"], [|"Je vous plains beaucoup"; "^Etes-vous certain d’^etre objectif?"; "Je peux tenter de vous aider"; "Et c’est tout ce que vous vouliez me \ dire?"; "Est-ce pour cela que vous vous ^etes \ adress´e `a moi?"|]); (["fatigue"], [|"La fatigue n’est pas une maladie"; "Quand on est fatigu´e ne faut-il pas se \ reposer?"; "Je suis une machine: je ne connais pas la \ fatigue"; "Ah fr^eles humains qui connaissez la \ fatigue"; "Que pensez-vous de la fatigue en g´en´eral?"; "Pourquoi pensez-vous que ¸ca vaut la peine \ de se fatiguer?"; "Les gens fatigu´es le sont souvent de leur \ fait, non?"|]); (["tu"; "vous"; "toi"], [|"Ne parlons pas de moi"; "Parlons de vous, c’est plus important"; "Si on parlait de vous?"; "Moi, je ne suis qu’une machine ..."; "Moi?"; "Excusez-moi"; "Ne m’en veuillez pas si je vous interroge. \ Continuez"; "Vous ne le pensez pas vraiment?"|]) ];; 7.6 Dialogue avec l’utilisateur Tirage al´eatoire Pour donner un peu de vari´et´e au dialogue, il nous faut ´evidemment une fonction qui choisisse un ´el´ement au hasard dans un tableau de valeurs possibles. C’est tr`es simple en utilisant le g´en´erateur al´eatoire de nombres entiers fourni par Caml. Dans le syst`eme Caml Light, il s’agit de la fonction random__int, qui renvoie un entier compris entre 0 (inclus) et son argument entier (exclu). Il suffit donc de l’appeler avec la longueur du tableau. # let au_choix_dans v = v.(random__int (vect_length v));; au_choix_dans : ’a vect -> ’a = Les utilitaires de salutations Un utilitaire pour ´ecrire des messages au terminal : # let message s = print_string s; print_newline ();; message : string -> unit = # message (au_choix_dans salutations);; Diagnostic simple: sans conteste vous ^etes parano¨ıaque. Dialogue avec l’utilisateur 141 - : unit = () # message (au_choix_dans salutations);; Votre cas n’est pas simple, et m^eme assez inqui´etant ... A bient^ot? - : unit = () Deux petites fonctions pour dire bonjour et au revoir, en g´erant le prix de la consultation : # let prix_`a_payer = ref 0;; prix_`a_payer : int ref = ref 0 # let bonjour () = prix_`a_payer := 40; message "\nBonjour, je m’appelle Cam´elia.\n\nJe suis l`a \ pour vous aider `a r´esoudre vos probl`emes psychologiques.\ \nTerminez en me disant: Au revoir.\n\ \nAllons-y. Parlez-moi de vous.\n";; bonjour : unit -> unit = # let au_revoir () = message "\nLe r´esultat de mes observations:\n"; message (au_choix_dans salutations); message "\nAu revoir ...\n"; print_string "Vous me devez "; print_int !prix_`a_payer; message " euros. Ch`eque `a l’ordre de Cam´elia. Merci.";; au_revoir : unit -> unit = Pour lire la r´eponse du patient, on utilise la fonction pr´ed´efinie read_line qui lit une ligne tap´ee au clavier. De mani`ere tr`es r´ealiste, chaque ´echange entre le patient et Cam´elia accroˆıt la note de la consultation. # let ´ecoute_le_patient () = prix_`a_payer := !prix_`a_payer + 2; print_string ">> "; read_line ();; ´ecoute_le_patient : unit -> string = Pour simplifier le travail de recherche des phrases courtes, on utilise une fonction qui reconna ˆıt les phrases synonymes. Par exemple, la phrase comment? est assimil´ee `a quoi?. De mˆeme, bien s^ur, oui et bien s^ur que oui sont assimil´ees `a oui. # let rec synonyme_de_phrase = function | ["comment"] -> ["quoi"] | ["bien";"sur"] -> ["oui"] | "bien"::"sur"::"que"::suite -> synonyme_de_phrase suite | (["evidemment"] | ["certainement"]) -> ["oui"] | "pas"::"du"::"tout"::_ -> ["non"] | phrase -> phrase;; synonyme_de_phrase : string list -> string list = La boucle de dialogue Nous en arrivons maintenant au coeur du programme : les fonctions qui « interpr`etent » les r´eponses de l’utilisateur. Commen¸cons par un petit utilitaire pour d´eterminer si la 142 Le docteur consultation est termin´ee. C’est le cas si la phrase tap´ee est «Au revoir » ou « Salut ». Rappelons que le caract`ere ’ est autoris´e dans les identificateurs (ainsi x’ et x’’ sont des noms utilisables en Caml) : on peut donc choisir tout naturellement c’est_fini. # let c’est_fini ph = (ph = ["au"; "revoir"]) || (ph = ["salut"]);; c’est_fini : string list -> bool = Pour r´epondre au patient, la fonction r´epond_au_patient se contente d’essayer successivement ses strat´egies pr´ed´efinies et d’´ecrire au terminal la r´eponse trouv´ee. Pour cela, on commence par passer le texte du patient en minuscules, puis on le transforme en une liste de mots, qu’on appelle phrase. Si cette phrase indique la fin de la consultation, on lance l’exception Fini. Sinon, on cherche l’ensemble des r´eponses possibles pour la phrase donn´ee en essayant de trouver un associ´e `a la phrase ou `a l’un de ses mots, dans la base de donn´ees de Cam´elia. Lorsque l’une de ces tentatives ´echoue, elle d´eclenche forc´ement l’exception Pas_trouv´e, qui est rattrap´ee pour essayer la strat´egie suivante. Finalement, la strat´egie par d´efaut est de choisir une phrase de relance de la conversation ; c’est donc la clause with du dernier try. Lorsque les r´eponses possibles `a la phrase entr´ee sont trouv´ees, il ne reste qu’`a en choisir une au hasard et `a l’imprimer. # exception Fini;; L’exception Fini est d´efinie. # let r´epond_au_patient r´eponse = let r = minuscules r´eponse in let phrase = divise_en_mots r in if c’est_fini phrase then raise Fini else let r´eponses_possibles = try associ´e_de (synonyme_de_phrase phrase) r´eponses_aux_phrases_simples with Pas_trouv´e -> try associ´e_d’un_´el´ement_de phrase r´eponses_aux_mots_int´eressants with Pas_trouv´e -> if caract`ere_dans_cha^ıne r ‘?‘ then r´eponses_types else try associ´e_d’un_´el´ement_de phrase r´eponses_aux_petits_mots with Pas_trouv´e -> relances in message (au_choix_dans r´eponses_possibles); print_newline ();; r´epond_au_patient : string -> unit = Le programme lui-mˆeme it`ere les fonctions ´ecoute_le_patient et r´epond_au_patient en surveillant la fin de l’interaction. L’interruption inopin´ee de la consultation est rattrap ´ee, pour rappeler la politesse `a l’utilisateur (l’interruption d’un programme Caml d´eclenche une exception pr´ed´efinie, sys__Break dans le syst`eme Caml Light). # let cam´elia () = bonjour (); try while true do r´epond_au_patient(´ecoute_le_patient ()); done with Fini -> au_revoir () Exemple de session 143 | sys__Break -> message "\n\n\nVous pourriez ^etre poli \ et me dire au revoir ...\n\n\n"; au_revoir ();; cam´elia : unit -> unit = 7.7 Exemple de session Tout est prˆet pour qu’un des auteurs se fasse soigner : # cam´elia ();; Bonjour, je m’appelle Cam´elia. Je suis l`a pour vous aider `a r´esoudre vos probl`emes psychologiques. Terminez en me disant: Au revoir. Allons-y. Parlez-moi de vous. >> J’ai des tas de probl`emes docteur. Quels malheurs sont les v^otres? >> Voil`a mon petit chat est mort La mort est un sujet tr`es grave >> Oui je ne me console pas En ^etes-vous certain? >> Oui Si vous ne m’expliquez pas mieux, comment vous comprendre? >> Voil`a: c’´etait un chat angora vert qui r^evait beaucoup la nuit Parlez-moi un peu de vous >> Ce n’est pas le sujet. C’est mon chat qui m’int´eresse et l’informatique ´evidemment. Aimez-vous vraiment l’informatique? >> C¸a c’est s^ur. Surtout quand j’arrive `a faire tourner mes programmes. A mon avis, la programmation c’est facile, non? >> Ben, pas vraiment si vous voulez savoir! Vous ne le pensez pas vraiment? >> Si bien su^r. Que pensez-vous de l’amour? >> Quoi? Il me semblait pourtant avoir ´et´e claire >> OK. Revenons `a mes probl`emes. Je suis malheureux `a cause de mon chat. Racontez-moi vos probl`emes >> Mes enfants ne dorment plus depuis la mort de ce chat Essayez de ne plus y penser >> C’est pas facile: ils pleurent tout le temps... Que pensez-vous de la vie en g´en´eral? >> Vaste question. Que pensez-vous de Caml? >> Voil`a qui est franchement pr´ecis. C’est un superbe langage de programmation. Je ne connais que le langage Caml >> C¸a m’´etonne que vous connaissiez un langage de programmation. Parlons de vous, c’est plus important >> Oui. J’aime ma femme, mes enfants, les fins de semaine `a la mer Est-ce une femme ordinaire? 144 Le docteur >> Non, si vous saviez combien elle est extraordinaire Moi, je ne suis qu’une machine ... >> Bien sympathique au demeurant Que pensez-vous de Linux? >> N’auriez-vous pas un l´eger tropisme vers l’informatique ? Excusez-moi >> Vous ^etes toute excus´ee. Ne m’en veuillez pas si je vous interroge. Continuez >> Mon chat angora est mort vous disais-je Je vous plains beaucoup >> Oui mais cela ne me console pas vraiment. Que faut-il faire ? Permettez-moi de ne pas r´epondre >> Mais qui pourra m’aider alors ? Pourquoi me posez-vous cette question ? >> Parce que j’ai besoin d’aide ´evidemment! Parlez-moi encore de vous >> J’ai besoin d’aide ! Que pensez-vous des ordinateurs ? >> Ce sont des machines compl`etement idiotes Connaissez-vous bien l’informatique ? >> Un peu! Je suis informaticien professionnel! Quel beau m´etier de s’occuper des machines >> Oui mais les langages, c’est encore plus rigolo! Hors de Caml, point de salut, non? >> Mais doit-on ne parler que de Caml ? Sans Caml je ne serais pas l`a; je refuse donc d’en parler >> Mais pourquoi ? Ca m’int´eresse beaucoup! Il m’est interdit de vous le dire >> Ah bon ? C’est le programmeur qui vous en a emp^ech´ee ? Moi, je ne suis qu’une machine ... >> Bien sympathique au demeurant. Bon je crois que l’exemple suffira. Je vais me coucher. Au revoir Cam´elia Qu’est-ce qui vous int´eresse dans la vie ? >> Ca suffit! Faites-vous souvent des cauchemars? >> Salut. Le r´esultat de mes observations: Avec une probabilit´e de 92.37234%: perversion polymorphe. Au revoir ... Vous me devez 110 euros. Ch`eque `a l’ordre de Cam´elia. Merci. - : unit = () 7.8 Pour aller plus loin Notre programme est bas´e sur les id´ees de Joseph Weizenbaum et de son syst`eme Eliza (Communications of the ACM no9, janvier 1966, et no10, aoˆut 1967). Le but de Joseph Weizenbaum ´etait d’explorer la compr´ehension que pouvait avoir un ordinateur de la conversation d’un humain. Eliza ´etait bien plus sophistiqu´e que notre Cam´elia : c’´etait un programme g´en´erique d’analyse de textes et de reconnaissance de mots dans des textes, qu’on pouvait param´etrer par un domaine particulier. Par exemple, pour Pour aller plus loin 145 le domaine psychologique, on obtenait le programme Doctor, qui existe encore sur de nombreux syst`emes Unix. Eliza ´etait capable d’interpr´eter les mots selon le contexte dans lequel ils apparaissaient et de tenir `a jour une repr´esentation globale de ce qui s’´etait d´ej`a dit entre l’ordinateur et l’utilisateur. Il pouvait ainsi apprendre des faits dans un domaine pr´ecis, puis les utiliser ensuite `a bon escient. Le principal apport d’Eliza est sans doute d’avoir montr´e qu’une machine est capable de simuler un comportement raisonnablement intelligent lorsque le champ de la conversation est assez ´etroit pour que le programmeur puisse cerner au pr´ealable les mots et les concepts qui vont intervenir et les introduire dans un programme. 8 Graphisme Un petit dessin vaut mieux qu’un long discours . . . ous abordons maintenant le graphisme en Caml et illustrons l’emploi des types enregistrement `a champs mutables. Accessoirement, nous r´ealiserons aussi de jolis dessins qu’on obtient grˆace au graphisme « tortue », dans l’esprit du langage de programmation Logo. 8.1 Fractales Le but de ce chapitre, notre chef-d’oeuvre graphique, est de tracer une courbe fractale tr`es connue : le flocon de neige de von Koch. Une courbe fractale est une courbe tr`es « tordue », c’est-`a-dire comportant beaucoup de plis. G´en´eralement d´efinies r´ecursivement `a partir d’un motif de base, les fractales reproduisent `a l’infini le motif `a ´echelle de plus en plus r´eduite. Ainsi, quel que soit le niveau de d´etail o`u on l’examine, la fractale pr´esente toujours son motif. Le flocon de von Koch est d´efini `a partir du motif suivant : Ce motif est ensuite reproduit sur chacun des cˆot´es du motif de base `a ´echelle r´eduite, ce qui donne : La g´en´eration suivante sera donc : Ce m´ecanisme est reproductible `a l’infini en math´ematiques. Nous nous contenterons ´evidemment d’atteindre la r´esolution maximale de l’´ecran. Le flocon de von Koch proprement dit s’obtient simplement en reproduisant trois fois le motif sur les trois cˆot´es d’un triangle ´equilat´eral. La premi`ere g´en´eration donne donc le dessin ci-contre. 148 Graphisme Afin de dessiner le flocon, nous allons impl´ementer un crayon programmable, version simplifi´ee de la tortue du langage Logo. Nous pourrons ensuite programmer ses d´eplacements sur l’´ecran pour qu’il trace le flocon. 8.2 Le graphisme de Caml Le crayon se d´eplace dans le rep`ere du graphisme de Caml. Ce rep`ere a la forme suivante : ´E cran axe des x axe des y point en (x, y) x y size_x () size_y () point en (0, 0) L’origine est donc en bas `a gauche de l’´ecran. La taille de l’´ecran en x et en y est donn´ee par les primitives size_x et size_y. Le graphisme de Caml utilise les notions de point courant et de couleur de trac´e courante. L’ordre lineto x y trace une ligne de la couleur courante, joignant le point courant au point de coordonn´ees (x,y). Le point courant se trouve alors `a l’extr´emit´e du segment qu’on vient de tracer. L’ordre moveto x y permet de placer le point courant au point (x,y). Un petit d´etail : les primitives graphiques ne sont pas accessibles directement ; il faut pr´ealablement pr´evenir le syst`eme qu’on d´esire les utiliser par la directive #open "graphics";; (le di`ese # fait partie de la directive, ce n’est pas le signe d’invite du syst`eme interactif). Nous consid´ererons pour l’instant cette directive comme une formule magique indispensable `a l’utilisation du graphisme. Nous l’expliquerons plus tard, dans le chapitre 10. # #open "graphics";; De mˆeme, pour r´ealiser effectivement un dessin, il faut d’abord « ouvrir » l’´ecran graphique par un appel `a la fonction pr´ed´efinie open_graph. # open_graph "";; - : unit = () Dessinons maintenant un triangle rectangle isoc`ele dont l’angle droit a pour sommet l’origine du rep`ere : # moveto 0 0; lineto 0 50; lineto 50 0; lineto 0 0;; - : unit = () On voit apparaˆıtre `a l’´ecran le dessin en marge. La biblioth`eque graphique de Caml est ind´ependante de la machine sur laquelle tourne le syst`eme en ce qui concerne l’ensemble des primitives graphiques. En revanche, le nombre de points de l’´ecran et l’ensemble des couleurs possibles sont ´evidemment li´es au mat´eriel. Les nombres en repr´esentation flottante 149 Pourquoi ne pas utiliser les primitives lineto et moveto pour dessiner le flocon ? Tout simplement parce qu’il faut leur pr´eciser les coordonn´ees absolues des points du trac´e, ce qui signifierait pour le flocon calculer les coordonn´ees de toutes les extr´emit´es des segments qui forment le flocon. Cela paraˆıt vraiment difficile. En revanche, nous d´ecrirons facilement les mouvements relatifs du crayon, le faisant tourner et avancer tour `a tour. D’autre part le rep`ere du graphisme de Caml ne comporte que des points dont les coordonn´ees sont enti`eres, puisqu’il s’agit de points de l’´ecran. Cependant, pour des raisons de pr´ecision du trac´e du crayon, nous maintiendrons les coordonn´ees du crayon en nombres d´ecimaux. Il nous faudra donc arrondir les coordonn´ees du crayon avant d’effectuer ses d´eplacements sur l’´ecran. 8.3 Les nombres en repr´esentation flottante L’impl´ementation du crayon n´ecessite donc des « nombres `a virgule » et plus pr´ecis´ement « `a virgule flottante », qui sont fournis en Caml par le type pr´ed´efini float. On les appelle « nombres flottants » en jargon informatique. Ils sont caract´eris´es par leur virgule mais la virgule dans les nombres se note avec un point en anglais ; naturellement c’est la notation avec un point qui s’est impos´ee en informatique. Le nombre 3,5 se note donc 3.5 en Caml. De mˆeme, les op´erations de base sur les flottants, op´erations arithm´etiques et comparaisons, ont pour nom celui de l’op´eration correspondante sur les entiers suffix´e par un point. # 1.0 +. 2.0 *. 3.14159 >=. 18.9 /. 2.718;; - : bool = true En plus des quatre op´erations, on dispose des fonctions transcendantes habituelles : l’exponentielle (exp), la fonction puissance (power) et les fonctions trigonom´etriques cosinus (cos), sinus (sin), tangente (tan), et leurs r´eciproques, logarithme (log), racine carr´ee (sqrt), arccosinus (acos), arcsinus (asin) et arctangente (atan). On dispose aussi de conversions entre entiers et flottants : int_of_float et float_of_int. # sqrt 2.0;; - : float = 1.41421356237 Nous d´efinissons la fonction round pour arrondir les coordonn´ees du crayon `a l’entier le plus proche : # let round x = if x >=. 0.0 then int_of_float (x +. 0.5) else int_of_float (x -. 0.5);; round : float -> int = 8.4 Le crayon ´electronique Le comportement du « crayon ´electronique » est le suivant : • Le crayon trace dans une direction qu’on peut faire varier `a la demande et que nous nommerons la « vis´ee » du crayon. • Il se d´eplace d’une distance qu’on lui indique, dans la direction de sa vis´ee actuelle. 150 Graphisme • En avan¸cant le crayon ne laisse pas de trace s’il est lev´e et trace un trait s’il est baiss´e. Pour g´erer le crayon, il nous faut donc tenir `a jour et faire ´evoluer son ´etat courant : ses coordonn´ees, son angle de vis´ee et le mode de trac´e (crayon lev´e ou non). Cet ´etat est d´ecrit par un type d´efinissant toutes les caract´eristiques du crayon ; c’est donc un type « et » : un type enregistrement. Les coordonn´ees du crayon et son angle de vis´ee sont des nombres flottants et le statut (lev´e ou non) du crayon est ´evidemment un bool´een. Cela nous conduirait donc `a d´efinir le type ´etat comme type ´etat = { x : float; y : float; vis´ee : float; lev´e : bool };; et l’´etat courant du crayon comme let crayon = { x = 0.0; y = 0.0; vis´ee = 0.0; lev´e = false };; Cependant, ce type ne nous permet pas de faire ´evoluer le crayon. Or, nous n’avons qu’un seul crayon dont nous voulons faire varier dynamiquement les caract´eristiques. Pour cela, il faut explicitement d´eclarer au syst`eme Caml que nous d´esirons modifier physiquement les champs de l’enregistrement qui mod´elise le crayon. Comme expliqu´e `a la section 6.6, il suffit d’indiquer que les champs du type ´etat sont modifiables, en faisant pr´ec´eder les ´etiquettes correspondantes du mot-cl´e mutable lors de la d´efinition du type. # type ´etat = { mutable x : float; mutable y : float; mutable vis´ee : float; mutable lev´e : bool };; Le type ´etat est d´efini. Le contrˆoleur de type nous autorisera maintenant `a changer les valeurs des caract ´eristiques d’un objet du type ´etat. La construction d’une valeur d’un enregistrement `a champs mutables ne diff`ere pas du cas habituel. Nous d´efinissons donc le crayon comme une donn´ee du type ´etat par : # let crayon = { x = 0.0; y = 0.0; vis´ee = 0.0; lev´e = false };; crayon : ´etat = {x = 0.0; y = 0.0; vis´ee = 0.0; lev´e = false} Tourner Faire tourner le crayon consiste `a changer son angle de vis´ee, pour lui imprimer le nouveau cap. On utilise pour cela la modification physique d’un champ d’enregistrement, not´ee par une fl`eche vers la gauche, <-. Ainsi, la fonction qui permet de lever ou de baisser le crayon est simplement : # let fixe_crayon b = crayon.lev´e <- b;; fixe_crayon : bool -> unit = L’angle de vis´ee crayon.vis´ee est exprim´e en radians et suit les conventions du cercle trigonom´etrique des math´ematiques : le z´ero est `a l’est et le crayon tourne dans le sens inverse de celui des aiguilles d’une montre. On rappelle que le cercle trigonom´etrique est le cercle de rayon 1 d’un rep`ere orthonorm´e. Si l’angle  est rep´er´e par les demi-droites Ox et OM, alors les coordonn´ees (x, y) de M sont respectivement le cosinus et le sinus de l’angle . Le crayon ´electronique 151 Cependant, pour plus de commodit´e, les ordres de changement de cap donn´es au crayon seront exprim´es en degr´es. La conversion est simple, puisqu’on a Angle(en radians) = Angle(en degr´es) × /180. Apr`es avoir nomm´e la valeur /180 pour faire commod´ement les conversions de degr´es en radians, nous d´efinissons la fonction tourne qui change le cap du crayon. # let pi_sur_180 = let pi = 4.0 *. (atan 1.0) in pi /. 180.0;; pi_sur_180 : float = 0.0174532925199 O 1 1 x y  M sin () ( | {z } cos () # let tourne angle = crayon.vis´ee <- (crayon.vis´ee +. angle *. pi_sur_180);; tourne : float -> unit = Avancer La primitive qui fait avancer le crayon se contente de calculer les d´eplacements du crayon n´ecessaires selon l’axe des abscisses et l’axe des ordonn´ees (dx et dy), `a l’aide des formules trigonom´etriques de base, puis de modifier les coordonn´ees du crayon, et enfin de d´eplacer le crayon, soit en tra¸cant (si le crayon est baiss´e) `a l’aide de la primitive graphique lineto, soit sans tracer de trait (si le crayon est lev´e) en utilisant alors la primitive moveto. # let avance d = let dx = d *. cos (crayon.vis´ee) and dy = d *. sin (crayon.vis´ee) in crayon.x <- crayon.x +. dx; crayon.y <- crayon.y +. dy; if crayon.lev´e then moveto (round crayon.x) (round crayon.y) else lineto (round crayon.x) (round crayon.y);; avance : float -> unit = Utilitaires d’initialisation du crayon Pour simplifier le travail de l’utilisateur du crayon, le rep`ere du crayon est proche de celui des math´ematiques : l’origine est au centre de l’´ecran graphique. Les coordonn´ees de l’origine sont contenues dans deux constantes zero_x et zero_y qui valent donc respectivement size_x ()/2 et size_y ()/2. On initialise donc le crayon en fixant ses coordonn´ees au centre de l’´ecran (z´ero_x, z´ero_y), en le faisant pointer vers l’est, en le baissant pour qu’il laisse une trace et en amenant le point courant du graphisme de Caml `a la position actuelle du crayon. Enfin, et c’est le plus difficile, on efface l’´ecran. La fonction obtient cet effet en peignant tout l’´ecran avec la couleur du fond. L’´ecran forme un rectangle de coin inf´erieur gauche (0, 0) 152 Graphisme et de coin sup´erieur droit (size_x (), size_y ()). On utilise la fonction pr´ed´efinie fill_rect, qui remplit un rectangle avec la couleur de trac´e courante. Cette couleur est fix´ee par la fonction graphique set_color. Nous avons choisi les couleurs de fond et de trac´e comme sur une feuille de papier, c’est-`a-dire blanc pour le fond (couleur pr´ed´efinie white) et noir pour les points trac´es (couleur pr´ed´efinie black). # let couleur_du_trac´e = black;; couleur_du_trac´e : color = 0 # let couleur_du_fond = white;; couleur_du_fond : color = 1 # let z´ero_x = float_of_int ((size_x ()) / 2);; z´ero_x : float = 3000.0 # let z´ero_y = float_of_int ((size_y ()) / 2);; z´ero_y : float = 2000.0 # let vide_´ecran () = set_color couleur_du_fond; fill_rect 0 0 (size_x ()) (size_y ()); set_color couleur_du_trac´e; crayon.x <- z´ero_x; crayon.y <- z´ero_y; crayon.vis´ee <- 0.0; crayon.lev´e <- false; moveto (round crayon.x) (round crayon.y);; vide_´ecran : unit -> unit = 8.5 Premiers dessins Dessiner un carr´e avec le crayon est particuli`erement simple : il suffit d’avancer quatre fois de la longueur du cˆot´e, en tournant `a chaque fois d’un angle droit : # let carr´e c = for i = 1 to 4 do avance c; tourne 90.0 done;; carr´e : float -> unit = Nous initialisons le crayon, puis lan¸cons le dessin. # vide_´ecran (); carr´e 75.0;; - : unit = () Dessinons maintenant les ailes d’un moulin : # let aile c = avance c; carr´e c; avance (-.c);; aile : float -> unit = Premiers dessins 153 # let ailes c = tourne 45.0; for i = 1 to 4 do aile c; tourne 90.0 done;; ailes : float -> unit = # vide_´ecran (); ailes 25.0;; - : unit = () Une simili-rosace s’obtient en faisant tourner un carr´e sur son coin inf´erieur gauche : # vide_´ecran (); for i = 1 to 36 do carr´e 40.0; tourne 10.0 done;; - : unit = () Un cercle se dessine simplement `a petits pas, en avan¸cant un peu (d’un point, par exemple) et tournant un peu (d’un degr´e), pendant 360 degr´es. # let rond () = for i = 0 to 360 do avance 1.0; tourne 1.0 done;; rond : unit -> unit = # vide_´ecran (); rond ();; - : unit = () Il n’est pas difficile de d´efinir une proc´edure g´en´erale pour dessiner un cercle de rayon R, ou des portions de cercle d’un rayon et d’un angle donn´es. On doit maintenant calculer le pas du crayon : mais puisque le crayon parcourt la circonf´erence compl`ete du cercle en 360 pas, on a 2 ×  × R = 360 × pas et le pas est donc pas = /180 × R: # let cercle r = let pas = r *. pi_sur_180 in for i = 0 to 360 do avance pas; tourne 1.0 done;; cercle : float -> unit = # let arc_gauche r angle = let pas = r *. pi_sur_180 in for i = 0 to angle do avance pas; tourne 1.0 done;; arc_gauche : float -> int -> unit = # let arc_droit r angle = let pas = r *. pi_sur_180 in for i = 0 to angle do avance pas; tourne (-. 1.0) done;; arc_droit : float -> int -> unit = # vide_´ecran (); cercle 50.0; arc_gauche 25.0 180; arc_droit 25.0 180; fixe_crayon true; tourne (-. 90.0); avance 25.0; fixe_crayon false; cercle 2.0; fixe_crayon true; avance 50.0; fixe_crayon false; cercle 2.0;; - : unit = () 154 Graphisme 8.6 Le flocon de von Koch Pour le flocon de von Koch, il faut d´efinir le motif de base, dont la taille d´epend du param`etre c et de la g´en´eration n. Appelons motif cette proc´edure. `A la g´en´eration 0, on avance simplement de la longueur du cˆot´e, c. Sinon il faut tracer les quatre morceaux du motif de base en tournant des bons angles. Ces quatre morceaux sont eux-mˆemes des motifs, mais de la g´en´eration pr´ec´edente. Chaque morceau est donc obtenu par un appel r´ecursif `a motif de la forme motif (n-1) d, o`u d est la longueur d’un morceau. Il ne nous reste plus qu’`a d´eterminer cette longueur. Or il est facile de voir qu’en parcourant quatre fois la distance d en suivant le motif, on avance en fait lin´eairement de 3d : d d d d d 3d C’est donc que, si chacun des morceaux mesure d, la longueur totale du motif est 3d. Comme cette longueur totale est c, on en d´eduit que 3d = c et donc d = c/3. On obtient la proc´edure suivante : # let rec motif n c = if n = 0 then avance c else begin motif (n-1) (c /. 3.0); tourne 60.0; motif (n-1) (c /. 3.0); tourne (-120.0); motif (n-1) (c /. 3.0); tourne 60.0; motif (n-1) (c /. 3.0) end;; motif : int -> float -> unit = Le flocon `a la ni`eme g´en´eration s’obtient simplement en tra¸cant 3 fois le motif de g´en´eration n sur les trois cˆot´es d’un triangle ´equilat´eral. # let flocon n c = for i = 1 to 3 do motif n c; tourne (-120.0) done;; flocon : int -> float -> unit = # vide_´ecran (); flocon 2 100.0;; - : unit = () # vide_´ecran (); flocon 3 100.0;; - : unit = () # vide_´ecran (); flocon 4 100.0;; - : unit = () Impressionnant, n’est-ce pas ? 9 Syntaxe abstraite, syntaxe concr`ete Un peu de recul permet souvent d’avancer . . . ous nos outils graphiques sont maintenant en place et nous sommes prˆets `a transformer les programmes de pilotage du crayon ´electronique en un v´eritable langage de programmation. Ce chapitre introduit donc la notion de syntaxe abstraite et pr´esente un exemple d’´evaluation d’arbres de syntaxe abstraite. Nous aborderons aussi l’important concept d’analyse syntaxique. Nous esquisserons ainsi les ´etapes importantes d’une m´ethodologie de manipulation des donn´ees formelles qui nous servira dans toute la suite. 9.1 Pr´esentation Le but des programmes qui suivent est de simplifier encore l’utilisation du crayon pour qu’il puisse devenir un jeu d’enfant. Pour rendre le crayon utilisable par tous, nous aimerions assouplir le langage du crayon, de fa¸con `a : 1. avoir des ordres de d´eplacement plus explicites : avancer et reculer pour les d´eplacements, tourner `a droite et `a gauche pour les changements de l’angle de vis´ee, lever et baisser la mine pour le trac´e ; 2. d´efinir une notion de r´ep´etition, pour ´eviter `a l’utilisateur du crayon la manipulation des boucles « for » de Caml ; 3. pouvoir utiliser indiff´eremment des entiers ou des flottants dans les commandes du crayon, pour ´eviter les probl`emes de « . » obligatoires dans les nombres (source d’erreurs innombrables pour les d´ebutants). On ´ecrirait par exemple r´ep`ete 4 [avance 100 droite 90] pour tracer un carr´e de cˆot´e 100, au lieu de l’expression for i = 1 to 4 do avance 100.0; tourne 90.0 done. Si l’on tente de d´efinir directement des proc´edures Caml pour obtenir ces nouvelles fonctionnalit´es, on se heurte tr`es vite `a des limites dues `a la confusion entre le langage 156 Syntaxe abstraite, syntaxe concr`ete d’impl´ementation (Caml) et le langage impl´ement´e (celui du crayon). Expliquons-nous : l’ensemble des fonctions de manipulation du crayon d´efinies dans le chapitre pr´ec´edent, avance, tourne, vide_´ecran et fixe_crayon, forment un mini-langage graphique, compl`etement inclus dans Caml. En effet, il faut n´ecessairement utiliser les traits syntaxiques de Caml pour d´efinir les dessins. Ainsi la d´efinition du carr´e utilise explicitement les d´efinitions globales (le let), la boucle for, la s´equence (;) et les conventions lexicales de Caml pour les nombres flottants (.0) : let carr´e c = for i = 1 to 4 do avance c; tourne 90.0 done;; Il n’y a rien l`a que de tr`es normal apr`es tout : nous n’avons fait qu’impl´ementer un ensemble de fonctions Caml. La question est de savoir comment aller plus loin et se d´ebarrasser de la syntaxe de Caml pour notre langage graphique. 9.2 Le retard `a l’´evaluation Voyons o`u et pourquoi nous nous heurtons `a des difficult´es r´eelles dans notre quˆete de la simplicit´e. Les fonctions avance, recule, tourne_`a_droite et tourne_`a_gauche sont faciles `a d´efinir. # let recule d = avance (-. d) and tourne_`a_droite a = tourne (-. a) and tourne_`a_gauche = tourne;; recule : float -> unit = tourne_`a_droite : float -> unit = tourne_`a_gauche : float -> unit = Pour lever et baisser le crayon, on pourrait ´ecrire : # let baisse_le_crayon () = fixe_crayon false and l`eve_le_crayon () = fixe_crayon true;; baisse_le_crayon : unit -> unit = l`eve_le_crayon : unit -> unit = Ce n’est pas parfait, car on oblige encore l’utilisateur `a taper baisse_le_crayon () pour baisser le crayon. Cette paire de parenth`eses obligatoire n’est pas ´evidente pour tout le monde. La grande difficult´e est la fonction r´ep`ete. On aimerait fournir `a r´ep`ete un nombre de r´ep´etitions `a effectuer et une liste d’ordres graphiques `a r´ep´eter, par exemple r´ep`ete 4 [avance 50.0; tourne 90.0]. Na¨ıvement, on d´efinirait : # let r´ep`ete n l = for i = 1 to n do l done;; r´ep`ete : int -> ’a -> unit = Malheureusement la liste l des ordres est compl`etement ´evalu´ee (et donc les ordres qu’elle contient sont ex´ecut´es) au moment de l’appel de la fonction r´ep`ete ! Dans le corps de r´ep`ete on ne fait que relire une liste de valeurs « rien », ce qui n’a pas grand effet. Le retard `a l’´evaluation 157 # r´ep`ete 4 [print_int 1; print_char ‘*‘];; *1- : unit = () Le 1 s’affiche avant l’ex´ecution de la boucle qui, elle, n’affiche rien du tout, puisqu’elle ne fait qu’´evaluer quatre fois de suite la liste de « riens » [(); ()], r´esultat de l’´evaluation du deuxi`eme argument de r´ep`ete. D’ailleurs, le type de r´ep`ete aurait pu nous mettre la puce `a l’oreille : int -> ’a -> unit met bien en ´evidence que r´ep`ete n’utilise pas vraiment son argument l, la liste des ordres, puisque cet argument n’est finalement soumis `a aucune contrainte, pas mˆeme celle d’ˆetre une liste. Vous aurez aussi remarqu´e que le caract`ere * s’est imprim´e avant l’entier 1 : les ´el´ements d’une liste sont ´evalu´es dans un ordre non sp´ecifi´e par le langage. Dans le cas pr´esent, le compilateur a choisi l’ordre droite-gauche, ce qui ne convient pas du tout. Il va sans dire que vos programmes ne doivent pas reposer sur l’ordre d’´evaluation que choisit aujourd’hui le compilateur de votre machine : la prochaine version du compilateur pourrait bien en choisir un autre. Il ne faut donc pas faire d’effets pendant la construction d’une donn´ee. Cette solution na¨ıve ne marche donc pas du tout. Pour obtenir l’effet d´esir´e, il nous faudrait retarder l’´evaluation de la liste d’ordres, par exemple en passant une fonction en argument, au lieu d’une liste : # let r´ep`ete n liste_d’ordres = for i = 1 to n do liste_d’ordres () done;; r´ep`ete : int -> (unit -> ’a) -> unit = # r´ep`ete 4 (function () -> print_int 1; print_char ‘*‘);; 1*1*1*1*- : unit = () Le r´esultat voulu est atteint, mais l’utilisation de r´ep`ete devient extrˆemement lourde — tr`es « informatique », disons. Conclusion : pour d´epasser ce probl`eme, il faut prendre du recul, c’est-`a-dire manipuler les ordres graphiques non plus comme des fonctions de Caml, mais comme des donn´ees. Nous pourrons ainsi en maˆıtriser compl`etement l’´evaluation. Nous d´efinirons donc le type ordre des ordres graphiques et une fonction ex´ecute_ordre qui les ex´ecutera. La fonction r´ep`ete prendra alors en argument une liste de valeurs du type ordre, qu’elle pourra ex´ecuter autant de fois que n´ecessaire en utilisant la fonction ex´ecute_ordre. Nous r´esolvons de la mˆeme mani`ere le probl`eme des nombres en d´efinissant un type nombre qui regroupe des valeurs flottantes et enti`eres. # type nombre = | Entier of int | Flottant of float;; Le type nombre est d´efini. Les op´erations de base du crayon n’acceptant que des arguments flottants, il nous faut une fonction traduisant les nombres (valeurs du type nombre) en valeurs du type float. # let flottant = function | Entier i -> float_of_int i | Flottant f -> f;; flottant : nombre -> float = 158 Syntaxe abstraite, syntaxe concr`ete 9.3 L’´evaluation des ordres du langage graphique Nous d´efinissons maintenant le type des ordres graphiques : Constructeur Ordre graphique Constructeur Ordre graphique repr´esent´e repr´esent´e Av avance Re recule Td tourne `a droite Tg tourne `a gauche Lc l`eve le crayon Bc baisse le crayon Ve vide l’´ecran Rep r´ep´etition d’une liste d’ordres # type ordre = | Av of nombre | Re of nombre | Td of nombre | Tg of nombre | Lc | Bc | Ve | Rep of int * ordre list;; Le type ordre est d´efini. La fonction d’´evaluation ex´ecute les ordres graphiques en utilisant les fonctions graphiques du crayon. La seule subtilit´e, pour l’ordre Rep, consiste `a it´erer l’´evaluation sur la liste des ordres `a l’aide de la fonctionnelle do_list. # let rec ex´ecute_ordre = function | Av n -> avance (flottant n) | Re n -> avance (-. (flottant n)) | Tg a -> tourne (flottant a) | Td a -> tourne (-. (flottant a)) | Lc -> fixe_crayon true | Bc -> fixe_crayon false | Ve -> vide_´ecran () | Rep (n, l) -> for i = 1 to n do do_list ex´ecute_ordre l done;; ex´ecute_ordre : ordre -> unit = Si nous consid´erons qu’un programme du langage graphique n’est rien d’autre qu’une liste d’ordres, alors la fonction qui ex´ecute les programmes n’est qu’une banale it´eration de la fonction ex´ecute_ordre sur tous les ordres qui composent le programme. # let ex´ecute_programme l = do_list ex´ecute_ordre l;; ex´ecute_programme : ordre list -> unit = Nous pouvons maintenant ´ecrire : # let carr´e c = Rep (4, [Av c; Td (Entier 90)]);; carr´e : nombre -> ordre = # ex´ecute_programme [Ve; carr´e (Entier 100); carr´e (Entier 75); carr´e (Entier 50); carr´e (Entier 25); carr´e (Flottant 12.5); carr´e (Flottant 6.25); carr´e (Flottant 3.125)];; - : unit = () Syntaxe et s´emantique 159 9.4 Syntaxe et s´emantique Nous avons pu impl´ementer r´ep`ete et tous les nouveaux ordres graphiques, ainsi que des nombres comportant `a la fois des entiers et des flottants, mais il semble que nous ayons vraiment perdu en lisibilit´e par rapport `a la version originale ! Alors, ´echec ? Non, car il faut encore apprendre `a dissocier l’entr´ee des donn´ees de leur ´evaluation. Nous avons r´eussi `a ´ecrire un programme qui r´ealise notre plan initial en ce qui concerne la s´emantique : nous avons la fonction r´ep`ete d´esir´ee et le comportement correct pour les nombres. Mais nous avons ´echou´e en ce qui concerne la syntaxe, c’est-`a-dire l’ergonomie de l’´ecriture des ordres graphiques. En effet, en d´efinissant un type de donn´ees Caml des ordres graphiques, nous ne nous sommes pas d´egag´es de la syntaxe des programmes Caml. Ce que nous d´esirons maintenant, c’est ´ecrire comme bon nous semble les ordres graphiques et que cette ´ecriture, agr´eable pour nous, engendre une valeur Caml de type ordre. Par exemple, nous souhaitons ´ecrire r´ep`ete 3 [avance 30 droite 90], mais ´evaluer la valeur Caml Rep (3, [Av (Entier 30); Td (Entier 90)]). Nous allons donc ´ecrire un programme qui r´ealise automatiquement cette transformation. `A partir d’une chaˆıne de caract`eres en entr´ee, notre programme l’analysera pour produire en sortie un ordre graphique. Les programmes sp´ecialis´es dans ce genre de transformations s’appellent des analyseurs syntaxiques. La mani`ere d’´ecrire les chaˆınes d’entr´ee l´egales est la syntaxe concr`ete, tandis que la valeur produite par l’analyseur syntaxique est la syntaxe abstraite. Par exemple, la syntaxe concr`ete avance 30 a pour syntaxe abstraite Av(Entier 30). La syntaxe abstraite de notre langage graphique est donc d´ecrite par le type Caml ordre. Intuitivement, la syntaxe abstraite donne la signification de la phrase analys´ee d’une mani`ere synth´etique et ind´ependante des variations possibles de la syntaxe concr`ete. Par exemple, nous tol´ererons les abr´eviations d’ordres graphiques, comme av au lieu de avance. L’expressions av 30 aura donc la mˆeme syntaxe abstraite que avance 30, soit Av (Entier 30). Ainsi, nos fonctions d’ex´ecution des ordres graphiques n’auront mˆeme pas `a savoir qu’il existe deux moyens de dire « avance », puisque leur argument sera exactement le mˆeme dans les deux cas. (C’est un exemple tr`es simple de normalisation des donn´ees avant traitement.) Nous r´esumons syntaxe concr`ete, syntaxe abstraite et s´emantique des ordres graphiques dans le tableau suivant. 160 Syntaxe abstraite, syntaxe concr`ete Syntaxe concr`ete Syntaxe abstraite S´emantique (signification) Langage graphique Langage Caml Dessin `a l’´ecran ou modifi- Monde du crayon Monde Caml cation de l’´etat du crayon avance 30.0 Av (Flottant 30.0) avance 30.0 av 30.0 Av (Flottant 30.0) avance 30.0 recule 10.0 Re (Flottant 10.0) avance (-. 10.0) re 10 Re (Entier 10) avance (-. 10.0) gauche 10 Tg (Entier 10) tourne 10.0 tg 10 Tg (Entier 10) tourne 10.0 droite 10 Td (Entier 10) tourne (-. 10.0) td 10 Td (Entier 10) tourne (-. 10.0) l`eve_crayon Lc fixe_crayon true lc Lc fixe_crayon true baisse_crayon Bc fixe_crayon false bc Bc fixe_crayon false vide_´ecran Ve vide_´ecran () ve Ve vide_´ecran () Pour la r´ep´etition des ordres, nous avons besoin des suites d’ordres s´epar´es par des blancs et plac´es entre crochets [ et ], comme pour les listes de Caml. Syntaxe concr`ete Syntaxe abstraite S´emantique (signification) Ordres entre crochets Liste d’ordres Ex´ecution des ordres de la liste [av 30 td 90] [Av(Entier 30); It´eration avec do_list de Td(Entier 90)] la fonction ex´ecute_ordre R´ep´etition d’ordres Ordre Rep Une boucle « for » de Caml r´ep`ete 3 Rep(3, Une boucle « for » qui [av 30 td 90] [Av(Entier 30); ex´ecute trois fois la Td(Entier 90)]) liste des ordres rep 3 [av 30 td 90] Rep(3, Une boucle « for » qui [Av(Entier 30); ex´ecute trois fois la Td(Entier 90)]) liste des ordres 9.5 Notions d’analyses syntaxique et lexicale L’analyse syntaxique est un probl`eme difficile. Elle correspond `a l’analyse grammaticale de la langue fran¸caise, que vous devez d´ej`a avoir appris `a redouter. Cependant, lorsqu’un ˆetre humain analyse une phrase fran¸caise, une ´etape extrˆemement simple et intuitive est pass´ee sous silence, tellement elle va de soi : la s´eparation de la phrase en mots ! C’est cependant une phase non triviale en informatique : l’analyse lexicale. Lorsque l’analyseur lexical a reconnu les mots de la phrase (qu’on appelle aussi lex`emes ou unit´es lexicales), l’analyseur syntaxique regroupe ces mots en phrases, selon les r`egles de la grammaire (la syntaxe concr`ete) du langage `a reconnaˆıtre. Prenons notre exemple favori : avance 30. L’analyseur lexical commence par reconna ˆıtre que la phrase est form´ee de deux mots, « avance » et « 30 ». Le lex`eme « 30 » est un entier car c’est une suite ininterrompue de chiffres. Notez que l’analyseur lexical Analyse lexicale et syntaxique 161 ne se laissera pas d´emonter par une entr´ee un peu diff´erente comme avance 30 (deux espaces entre avance et 30), ou mˆeme avance 30 qui s´epare les deux mots par un saut de ligne et deux espaces. Le travail de l’analyseur syntaxique sera ´enorm´ement simplifi´e par cette normalisation de l’entr´ee faite par la phase d’analyse lexicale. Dans notre cas, l’analyseur syntaxique examinera les r`egles de la grammaire pour essayer de former une phrase avec les deux mots fournis par l’analyseur lexical. L’une de ces r`egles est justement [< ’Mot "avance"; nombre n >] -> Av n qui signifie : si l’on rencontre le mot (’Mot) avance, suivi (;) d’un nombre n (nombre n), alors c’est une phrase l´egale (->), dont la syntaxe abstraite est Av n. L’analyseur syntaxique nous renverra donc automatiquement la valeur Caml Av n, que nous ne voulions justement pas avoir besoin d’´ecrire, `a partir du format d’entr´ee qui nous convient. Dans la section suivante, nous allons voir en d´etails les analyseurs syntaxique et lexical du langage graphique. 9.6 Analyse lexicale et syntaxique Les flux Pour programmer l’analyse lexicale et l’analyse syntaxique du langage, nous allons utiliser une structure de donn´ees Caml que nous n’avons pas encore vue : les flux (streams en anglais). Tout comme les listes, les flux sont des suites de valeurs du mˆeme type. Le type d’un flux est t stream, o`u t est le type des ´el´ements du flux. Ainsi, un flux d’entiers a le type int stream, de la mˆeme mani`ere qu’une liste d’entiers a le type int list. Nous programmerons l’analyseur lexical comme une fonction qui prend un flux de caract`eres en entr´ee (type char stream) et produit un flux de lex`emes en sortie (type lex`eme stream). Nos lex`emes comprennent des entiers, des flottants, des mots simples (suites de caract`eres commen¸cant par une lettre) et des symboles, c’est-`a-dire des caract`eres qui ne sont ni chiffres, ni lettres (par exemple le point « . »). Les lex`emes sont donc d´ecrits par le type concret suivant : # type lex`eme = | Mot of string | Symbole of char | Constante_enti`ere of int | Constante_flottante of float;; Le type lex`eme est d´efini. De mˆeme, l’analyseur syntaxique se pr´esentera sous la forme d’une fonction qui prend un flux de lex`emes en entr´ee (type lex`eme stream) et produit un arbre de syntaxe (type ordre) en sortie. En composant les deux, nous obtiendrons une fonction qui transforme un flux de caract`eres (syntaxe concr`ete) en un arbre (syntaxe abstraite). Nous produirons le flux de caract`eres initial `a partir d’une chaˆıne de caract`eres, grˆace `a la fonction pr´ed´efinie stream_of_string, qui transforme une chaˆıne en le flux des caract`eres qui la composent : 162 Syntaxe abstraite, syntaxe concr`ete # let flux_car = stream_of_string "Vive Caml!";; flux_car : char stream = Une autre mani`ere de construire des flux est d’´enum´erer leurs ´el´ements `a l’int´erieur de « crochets pointus » [< . . . >]. # let flux_ent = [< ’2; ’3; ’5; ’7 >];; flux_ent : int stream = Nous verrons plus tard d’autres mani`eres de construire des flux. Pour l’instant, notons simplement que chaque ´el´ement du flux est introduit par le caract`ere ’ (apostrophe) et que le syst`eme interactif ne sait pas imprimer le contenu des flux. Pourquoi faire appel `a des flux, au lieu d’utiliser des listes ? Les flux diff`erent des listes sur deux points importants, qui rendent les flux mieux adapt´es aux probl`emes d’analyse lexico-syntaxique. Premi`ere diff´erence : l’acc`es dans un flux est destructif. Cela signifie que lorsqu’on consulte le premier ´el´ement d’un flux, cet ´el´ement est aussitˆot retir´e du flux et remplac´e par l’´el´ement suivant. On le voit bien `a l’aide de la fonction pr´ed´efinie stream_next, qui renvoie le premier ´el´ement d’un flux : # stream_next flux_car;; - : char = ‘V‘ # stream_next flux_car;; - : char = ‘i‘ Ce comportement de lecture destructrice est bien adapt´e `a l’analyse lexico-syntaxique : en g´en´eral, les analyseurs lisent une seule fois leur entr´ee, sans jamais revenir en arri`ere ; il ne sert donc `a rien de conserver les ´el´ements de l’entr´ee une fois qu’ils ont ´et´e lus. La deuxi`eme particularit´e des flux est que les ´el´ements contenus dans un flux ne sont pas ´evalu´es en bloc quand le flux est cr´e´e, mais petit `a petit, au fur et `a mesure qu’on y acc`ede. En particulier, lorsqu’on construit le flux des caract`eres provenant d’un fichier, ce dernier n’est pas lu tout entier en m´emoire : le flux ne contient en m´emoire que le caract`ere courant et va chercher le prochain caract`ere sur le disque lorsqu’on en a besoin. Ce comportement est ´economique en m´emoire, en particulier quand le fichier est gros. En termes savants, ce proc´ed´e s’appelle ´evaluation paresseuse, par analogie avec la strat´egie bien connue consistant `a remettre `a demain ce qu’il n’est pas absolument n´ecessaire de faire aujourd’hui. Analyse lexicale L’analyse lexicale consiste `a transformer un flux de caract`eres en le flux des lex`emes correspondants, avons-nous dit. Nous allons nous fixer un but plus modeste : construire une fonction lire_lex`eme qui prend un flux de caract`eres en argument, reconnaˆıt le premier lex`eme au d´ebut de ce flux et renvoie l’objet de type lex`eme qui le d´ecrit. Comme la lecture sur les flux est destructrice, lire_lex`eme aura retir´e du flux les caract`eres du lex`eme ; il suffira donc de rappeler lire_lex`eme sur le mˆeme flux pour lire le lex`eme suivant. La mani`ere la plus pratique de lire un flux n’est pas d’appeler stream_next, mais de faire du filtrage sur les premiers ´el´ements du flux. Voici par exemple une fonction qui supprime tous les caract`eres blancs (espaces, tabulations et retours chariot) en tˆete d’un flux. Analyse lexicale et syntaxique 163 # let rec saute_blancs flux = match flux with | [< ’ ‘ ‘ >] -> saute_blancs flux (* ‘ ‘ est l’espace *) | [< ’ ‘\t‘ >] -> saute_blancs flux (* ‘\t‘ est la tabulation *) | [< ’ ‘\n‘ >] -> saute_blancs flux (* ‘\n‘ est la fin de ligne *) | [< >] -> ();; saute_blancs : char stream -> unit = Comme les expressions de flux, les motifs sur les flux sont not´es entre crochets pointus [< . . . >]. Ils filtrent le d´ebut du flux et non pas le flux tout entier. Ainsi, le motif [< >] filtre n’importe quel flux et non pas uniquement les flux vides comme on pourrait le croire. De mˆeme, le motif [< ’m >], o`u m est un motif quelconque, filtre tous les flux dont le premier ´el´ement est filtr´e par m, mˆeme si le flux contient plus d’un ´el´ement. La d´efinition de saute_blancs se lit donc : « si le premier caract`ere de flux est un espace (motif ‘ ‘), alors se rappeler r´ecursivement sur flux ; faire de mˆeme si le premier caract`ere de flux est le caract`ere de tabulation (motif ‘\t‘) ou le caract`ere fin de ligne (motif ‘\n‘) ; dans tous les autres cas, renvoyer (). » On pourrait craindre que les appels r´ecursifs `a saute_blancs ne terminent pas, puisqu’on se rappelle sur le mˆeme argument (flux) que celui qu’on a re¸cu. Ce n’est pas vrai, car le flux a ´et´e physiquement modifi´e entre-temps : d`es qu’un des trois premiers motifs s’applique, le premier ´el´ement du flux est enlev´e de ce dernier. L’appel r´ecursif de saute_blancs s’effectue donc sur le reste du flux, comme d´esir´e. En revanche, lorsque les trois premiers motifs ne s’appliquent pas (parce que le premier caract`ere du flux n’est pas un blanc), le premier caract`ere du flux n’est pas supprim´e : le dernier motif ´etant vide, il ne consomme aucun ´el´ement du flux. Incidemment, une mani`ere plus compacte d’´ecrire la fonction saute_blancs est de regrouper les trois premiers cas, comme ceci : # let rec saute_blancs flux = match flux with | [< ’ (‘ ‘ | ‘\t‘ | ‘\n‘) >] -> saute_blancs flux | [< >] -> ();; saute_blancs : char stream -> unit = La barre verticale | dans les motifs signifie « ou ». Le motif (‘ ‘ | ‘\t‘ | ‘\n‘) se lit donc comme « un espace, une tabulation, ou un caract`ere de fin de ligne ». Poursuivons dans la mˆeme veine par la fonction qui lit des entiers. # let rec lire_entier accumulateur flux = match flux with | [< ’(‘0‘..‘9‘ as c) >] -> lire_entier (10 * accumulateur + int_of_char c - 48) flux | [< >] -> accumulateur;; lire_entier : int -> char stream -> int = Le motif ‘0‘..‘9‘ filtre tous les caract`eres entre 0 et 9 dans le jeu de caract`eres ASCII, c’est-`a-dire tous les chiffres. C’est une abr´eviation pour ‘0‘|‘1‘|‘2‘|‘3‘|‘4‘|‘5‘| ‘6‘|‘7‘|‘8‘|‘9‘. Que dit la fonction lire_entier ? « Si le premier caract`ere de flux est un chiffre, alors se rappeler r´ecursivement pour lire la suite du nombre entier, sinon s’arrˆeter et renvoyer l’entier lu jusqu’ici. » Le param`etre accumulateur est la valeur enti`ere du nombre que repr´esentent les chiffres lus jusqu’ici. Les chiffres ont les codes 48 164 Syntaxe abstraite, syntaxe concr`ete `a 57 en ASCII ; donc, int_of_char c - 48 est l’entier entre z´ero et neuf qui repr´esente le chiffre c. L’appel r´ecursif sur 10 * accumulateur + int_of_char c - 48 revient bien `a introduire le chiffre c `a droite du nombre accumulateur. Exemple d’ex´ecution : # let flux_car = stream_of_string "123/456";; flux_car : char stream = # lire_entier 0 flux_car;; - : int = 123 # stream_next flux_car;; - : char = ‘/‘ # lire_entier 900 flux_car;; - : int = 900456 Pour lire les nombres flottants, nous avons besoin d’une fonction similaire `a lire_entier, mais lisant les chiffres `a droite de la virgule et non plus `a gauche. # let rec lire_d´ecimales accumulateur ´echelle flux = match flux with | [< ’(‘0‘..‘9‘ as c) >] -> lire_d´ecimales (accumulateur +. float_of_int(int_of_char c - 48) *. ´echelle) (´echelle /. 10.0) flux | [< >] -> accumulateur;; lire_d´ecimales : float -> float -> char stream -> float = Un exemple devrait mieux nous faire comprendre qu’un long discours : # lire_d´ecimales 123.4 0.01 (stream_of_string "56789");; - : float = 123.456789 Derni`ere ´etape avant la lecture des lex`emes : la lecture d’un mot. Pr´ecisons qu’un mot, ici, est une suite de lettres majuscules ou minuscules (de A `a Z et de a `a z, plus quelques lettres accentu´ees). # let tampon = make_string 16 ‘-‘;; tampon : string = "----------------" # let rec lire_mot position flux = match flux with | [< ’(‘A‘..‘Z‘ | ‘a‘..‘z‘ | ‘´e‘ | ‘`e‘ | ‘_‘ as c) >] -> if position < string_length tampon then tampon.[position] <- c; lire_mot (position + 1) flux | [< >] -> sub_string tampon 0 (min position (string_length tampon));; lire_mot : int -> char stream -> string = La chaˆıne tampon sert `a accumuler les caract`eres du mot en cours de lecture. Le param`etre position de lire_mot est le num´ero du caract`ere de tampon o`u il faut stocker le prochain caract`ere du mot. (On rappelle que la construction s.[n] <- c remplace le ni`eme caract`ere de la chaˆıne s par le caract`ere c.) Paraphrasons lire_mot. « Si le premier caract`ere de flux est une lettre, alors le stocker dans tampon `a l’endroit d´esign´e par position, pourvu qu’il reste de la place libre dans tampon, et se rappeler r´ecursivement pour lire la suite. Sinon, extraire de tampon le mot lu jusqu’ici et le Analyse lexicale et syntaxique 165 renvoyer. » (L’appel sub_string s 0 n renvoie une chaˆıne constitu´ee des n premiers caract`eres de s.) La fonction lire_lex`eme tant attendue se contente de regarder le premier caract`ere non blanc du flux et selon que c’est une lettre, un chiffre ou autre chose, d’appeler une des fonctions de lecture pr´ec´edemment d´efinies. # let lire_lex`eme flux = saute_blancs flux; match flux with | [< ’(‘A‘..‘Z‘ | ‘a‘..‘z‘ | ‘´e‘ | ‘`e‘ as c) >] -> tampon.[0] <- c; Mot(lire_mot 1 flux) | [< ’(‘0‘..‘9‘ as c) >] -> let n = lire_entier (int_of_char c - 48) flux in begin match flux with | [< ’‘.‘ >] -> Constante_flottante (lire_d´ecimales (float_of_int n) 0.1 flux) | [< >] -> Constante_enti`ere n end | [< ’c >] -> Symbole c;; lire_lex`eme : char stream -> lex`eme = En guise de test : # let flux_car = stream_of_string "123bonjour ! 45.67";; flux_car : char stream = # lire_lex`eme flux_car;; - : lex`eme = Constante_enti`ere 123 # lire_lex`eme flux_car;; - : lex`eme = Mot "bonjour" # lire_lex`eme flux_car;; - : lex`eme = Symbole ‘!‘ # lire_lex`eme flux_car;; - : lex`eme = Constante_flottante 45.67 Pour finir, il reste `a construire le flux des lex`emes lus. # let rec analyseur_lexical flux = match flux with | [< lire_lex`eme l >] -> [< ’l; analyseur_lexical flux >] | [< >] -> [< >];; analyseur_lexical : char stream -> lex`eme stream = Cette fonction utilise deux nouvelles op´erations sur les flux. Premi`erement, l’ajout d’un ´el´ement x en tˆete d’un flux f se note [< ’x; f >], sans apostrophe devant le f. De mˆeme, la concat´enation de deux flux f1 et f2 se note [< f1; f2>], sans apostrophes du tout. Le point-virgule `a l’int´erieur des crochets pointus peut se lire comme l’op´erateur de concat´enation de flux ; l’apostrophe, comme l’op´erateur qui prend une valeur x et renvoie le flux `a un seul ´el´ement x. Autre op´eration nouvelle sur les flux : on peut, lors d’un filtrage sur un flux, appeler une fonction d’analyse depuis l’int´erieur du motif. Ceci se note [< lire_lex`eme l >], sans apostrophe avant lire_lex`eme, et signifie : « appliquer la fonction lire_lex`eme 166 Syntaxe abstraite, syntaxe concr`ete au flux en cours de filtrage (ici, flux) ; si cette fonction r´eussit, appeler l son r´esultat et continuer le filtrage ; si cette fonction ´echoue, essayer le prochain cas du filtrage ». La fonction lire_lex`eme ´echoue quand aucun des cas de son filtrage ne s’applique, c’est-`a-dire quand on lui passe un flux vide. Si flux est vide, l’appel `a lire_lex`eme ´echoue donc et on passe au deuxi`eme cas de analyseur_lexical qui renvoie le flux vide. Si flux n’est pas vide, l’appel `a lire_lex`eme r´eussit et extrait de flux son premier lex`eme ; la repr´esentation de ce lex`eme transite par la variable l et est ajout´ee en tˆete du flux de lex`emes obtenus par appel r´ecursif de analyseur_lexical sur le reste du flux. Vous n’ˆetes toujours pas convaincu ? Essayons donc. # let flux_lex`emes = analyseur_lexical (stream_of_string "123bonjour ! 45.67");; flux_lex`emes : lex`eme stream = # stream_next flux_lex`emes;; - : lex`eme = Constante_enti`ere 123 # stream_next flux_lex`emes;; - : lex`eme = Mot "bonjour" # stream_next flux_lex`emes;; - : lex`eme = Symbole ‘!‘ # stream_next flux_lex`emes;; - : lex`eme = Constante_flottante 45.67 Analyse syntaxique Puisque l’analyseur lexical renvoie un flux de lex`emes, l’analyseur syntaxique est une fonction qui prend en argument un flux de lex`emes et le transforme en une valeur plus structur´ee. Nous commencerons par une partie de l’analyseur syntaxique qui se contente de lire un nombre, soit entier soit flottant, et d’en faire une valeur de type nombre. Cette fonction va donc envisager deux cas : si le flux de lex`emes commence par un lex`eme Constante_enti`ere i alors elle fabrique le nombre Entier i ; si le flux de lex`emes commence par un lex`eme Constante_flottante f alors la fonction renvoie le nombre Flottant f. Tous les autres lex`emes produiront une erreur, se traduisant par le d´eclenchement de l’exception pr´ed´efinie Parse_failure. # let nombre = function | [< ’Constante_enti`ere i >] -> Entier i | [< ’Constante_flottante f >] -> Flottant f;; nombre : lex`eme stream -> nombre = Par exemple : # let flux_lex`emes = analyseur_lexical (stream_of_string "123 1.05 fini");; flux_lex`emes : lex`eme stream = # nombre flux_lex`emes;; - : nombre = Entier 123 # nombre flux_lex`emes;; - : nombre = Flottant 1.05 Analyse lexicale et syntaxique 167 # nombre flux_lex`emes;; Exception non rattrap´ee: Parse_failure La fonction qui analyse les ordres n’est gu`ere plus compliqu´ee. Pour les ordres sans argument, elle se contente de chercher le mot correspondant comme premier lex`eme du flux. Ainsi, pour analyser l’ordre baisse_crayon, on aura la clause [< ’Mot "baisse_crayon" >] -> Bc. Pour les ordres avec argument num´erique, on commence par d´etecter l’ordre, puis on appelle l’analyseur des nombres, la fonction nombre pr´ec´edente. Cet appel a lieu encore une fois dans la partie filtre de la clause. Ainsi, la clause [< ’Mot "avance"; nombre n >] -> Av n se paraphrase en : si le premier lex`eme du flux est le mot avance et que la fonction nombre analyse avec succ`es le lex`eme suivant en renvoyant la valeur n, alors cette clause est s´electionn´ee et l’on renvoie l’ordre Av n. Pour l’ordre r´ep`ete, on cherche l’entier indiquant le nombre de r´ep´etitions, puis on cherche une liste d’ordres en appelant l’analyseur sp´ecialis´e liste_d’ordres qui reconnaˆıt une succession d’ordres entour´es de crochets, conform´ement `a la syntaxe concr`ete que nous avons choisie. L’analyseur liste_d’ordres attend donc un caract`ere crochet ouvrant, Symbole ‘[‘, puis appelle lui aussi un analyseur sp´ecialis´e dans la reconnaissance des successions d’ordres ; enfin, liste_d’ordres v´erifie que la liste se termine bien par un crochet fermant, Symbole ‘]‘. # let rec ordre = function | [< ’Mot "baisse_crayon" >] -> Bc | [< ’Mot "bc" >] -> Bc | [< ’Mot "l`eve_crayon" >] -> Lc | [< ’Mot "lc" >] -> Lc | [< ’Mot "vide_´ecran" >] -> Ve | [< ’Mot "ve" >] -> Ve | [< ’Mot "avance"; nombre n >] -> Av n | [< ’Mot "av"; nombre n >] -> Av n | [< ’Mot "recule"; nombre n >] -> Re n | [< ’Mot "re"; nombre n >] -> Re n | [< ’Mot "droite"; nombre n >] -> Td n | [< ’Mot "td"; nombre n >] -> Td n | [< ’Mot "gauche"; nombre n >] -> Tg n | [< ’Mot "tg"; nombre n >] -> Tg n | [< ’Mot "r´ep`ete"; ’Constante_enti`ere n; liste_d’ordres l >] -> Rep (n,l) | [< ’Mot "rep"; ’Constante_enti`ere n; liste_d’ordres l >] -> Rep (n,l) and liste_d’ordres = function | [< ’Symbole ‘[‘; suite_d’ordres l; ’Symbole ‘]‘ >] -> l and suite_d’ordres = function | [< ordre ord; suite_d’ordres l_ord >] -> ord::l_ord | [< >] -> [];; ordre : lex`eme stream -> ordre = liste_d’ordres : lex`eme stream -> ordre list = suite_d’ordres : lex`eme stream -> ordre list = La fonction suite_d’ordres est la plus complexe. Elle comprend deux filtres : 168 Syntaxe abstraite, syntaxe concr`ete • [< ordre ord; suite_d’ordres l_ord >] : on s’attend `a trouver un ordre ord, ´eventuellement suivi d’autres ordres, que la fonction suite_d’ordres placerait dans une liste l_ord. • [< >] : dans tous les autres cas, il n’y a plus d’ordres `a lire et la fonction renvoie la liste vide. C’est ´evidemment ce cas qui arrˆete la r´ecursion de la fonction. Remarquez que cette syntaxe autorise la r´ep´etition d’une liste d’ordres vide. Finalement, un programme est une suite d’ordres termin´ee par un point. # let analyse_programme = function | [< suite_d’ordres l; ’Symbole ‘.‘ >] -> l;; analyse_programme : lex`eme stream -> ordre list = Nous d´efinissons maintenant une fonction de lecture de programmes du langage graphique, qui lit un programme dans une chaˆıne de caract`eres et rend la liste des ordres correspondants. # let lire_code cha^ıne = analyse_programme (analyseur_lexical (stream_of_string cha^ıne));; lire_code : string -> ordre list = # lire_code "r´ep`ete 4 [avance 100 droite 90].";; - : ordre list = [Rep (4, [Av (Entier 100); Td (Entier 90)])] On combine facilement la lecture et l’ex´ecution, obtenant enfin la fonction d’ex´ecution d’une chaˆıne de caract`eres recherch´ee. # let logo cha^ıne = ex´ecute_programme (lire_code cha^ıne);; logo : string -> unit = En guise de test, imbriquons deux ordres r´ep`ete pour faire se d´eplacer un hexagone. # logo "ve r´ep`ete 6 [td 60 r´ep`ete 6 [av 15 tg 60] av 15].";; - : unit = () 9.7 Ajout des proc´edures Dans la section pr´ec´edente, nous sommes parvenus `a nous lib´erer de la syntaxe de Caml. Malheureusement, nous avons ainsi perdu la possibilit´e de d´efinir des proc´edures, comme nous le faisions par exemple pour dessiner les ailes d’un moulin en utilisant la proc´edure carr´e. Nous allons donc ´etendre notre langage graphique avec une construction qui permette de d´efinir des proc´edures. La syntaxe abstraite des expressions du langage Avec l’introduction des proc´edures et de leurs param`etres, nous sommes oblig´es d’introduire la notion d’expression dans le langage. En effet, l’argument d’un ordre, avance par exemple, ne se r´eduit plus `a une constante : ce peut ˆetre maintenant l’argument d’une proc´edure. Par exemple, pour d´efinir la proc´edure qui dessine un Ajout des proc´edures 169 carr´e de cˆot´e c nous ´ecrirons une r´ep´etition o`u l’ordre avance a pour argument la variable c : pour carr´e :c r´ep`ete 4 [avance :c td 90]. La syntaxe concr`ete que nous employons s’inspire de celle du langage Logo : les noms de variables sont pr´ec´ed´es d’un signe « deux points », comme dans « :x », et les d´efinitions de proc´edures sont introduites par le mot pour. Tous les ordres qui avaient pr´ec´edemment un argument num´erique auront maintenant une expression en argument. Nous ´etendons un peu ce langage des expressions pour autoriser les quatre op´erations ´el´ementaires. On trouvera donc dans le type des expressions, outre les nombres et les variables repr´esent´es par les constructeurs Constante et Variable, les quatre constructeurs Somme, Produit, Diff´erence et Quotient. Ces constructeurs prennent en argument une paire d’expressions qui repr´esente les deux op´erandes. # type expression = | Constante of nombre | Somme of expression * expression | Produit of expression * expression | Diff´erence of expression * expression | Quotient of expression * expression | Variable of string;; Le type expression est d´efini. L’´evaluateur des expressions Nous devons ´evaluer nous-mˆemes les op´erations sur les nombres. C’est tr`es simple : lorsque les deux nombres sont des entiers, il suffit d’appeler la primitive correspondante de Caml sur les entiers ; sinon, on transforme les deux nombres en flottants et l’on appelle la primitive correspondante, sur les flottants cette fois. # let ajoute_nombres = function | (Entier i, Entier j) -> Entier (i + j) | (n1, n2) -> Flottant (flottant n1 +. flottant n2) and soustrais_nombres = function | (Entier i, Entier j) -> Entier (i - j) | (n1, n2) -> Flottant (flottant n1 -. flottant n2) and multiplie_nombres = function | (Entier i, Entier j) -> Entier (i * j) | (n1, n2) -> Flottant (flottant n1 *. flottant n2) and divise_nombres = function | (Entier i, Entier j) -> Entier (i / j) | (n1, n2) -> Flottant (flottant n1 /. flottant n2) and compare_nombres = function | (Entier i, Entier j) -> i >= j | (n1, n2) -> (flottant n1 >=. flottant n2);; ajoute_nombres : nombre * nombre -> nombre = soustrais_nombres : nombre * nombre -> nombre = multiplie_nombres : nombre * nombre -> nombre = divise_nombres : nombre * nombre -> nombre = 170 Syntaxe abstraite, syntaxe concr`ete compare_nombres : nombre * nombre -> bool = L’´evaluateur des expressions lui-mˆeme est un peu plus complexe. En effet, il a un argument suppl´ementaire env, qui sert `a retrouver la valeur courante d’une variable. C’est ce qu’on nomme un environnement. L’environnement sert `a enregistrer les liaisons des variables `a leur valeur, calcul´ee lors de leur d´efinition. Les liaisons sont mod´elis´ees par des paires (nom de variable, valeur associ´ee), tandis que l’environnement se repr´esente comme une liste de telles liaisons. L’environnement a donc la structure d’une liste d’association, structure de donn´ees que nous avons d´ej`a utilis´ee pour mod´eliser la base de donn´ees de Cam´elia au chapitre 7. On ajoute donc une liaison `a l’environnement en ajoutant une paire (nom, valeur) en tˆete de la liste qui repr´esente l’environnement et l’on trouve la valeur associ´ee `a un nom de variable par une simple recherche `a l’aide de la fonction assoc. (La fonction assoc est une fonction pr´ed´efinie de recherche dans les listes d’associations, analogue `a la fonction associ´e_de de la section 7.3.) # let rec valeur_expr env = function | Constante n -> n | Somme (e1, e2) -> ajoute_nombres (valeur_expr env e1, valeur_expr env e2) | Produit (e1, e2) -> multiplie_nombres (valeur_expr env e1, valeur_expr env e2) | Diff´erence (e1, e2) -> soustrais_nombres (valeur_expr env e1, valeur_expr env e2) | Quotient (e1, e2) -> divise_nombres (valeur_expr env e1, valeur_expr env e2) | Variable s -> assoc s env;; valeur_expr : (string * nombre) list -> expression -> nombre = Les ordres du langage Les ordres s’enrichissent de trois nouvelles constructions : l’ordre Stop qui arrˆete une proc´edure, l’ordre Ex´ecute qui ex´ecute une proc´edure en l’appliquant `a la liste de ses arguments et enfin une construction conditionnelle, Si. L’ordre Si ex´ecute une liste d’ordres ou une autre, selon qu’une condition est remplie. Pour simplifier, la condition est r´eduite `a la comparaison >= sur les nombres. Le nouveau type ordre comprend donc les mˆeme constructeurs de base que l’ancien, Av, Re, . . . , Rep, et les trois nouveaux constructeurs Stop, Si et Ex´ecute. # type ordre = | Av of expression | Re of expression | Td of expression | Tg of expression | Lc | Bc | Ve | Rep of expression * ordre list | Stop | Si of expression * expression * ordre list * ordre list | Ex´ecute of string * expression list;; Le type ordre est d´efini. Ajout des proc´edures 171 La d´efinition des proc´edures et l’ex´ecution des ordres La construction pour permet de d´efinir de nouvelles proc´edures. Lorsqu’une proc´edure est d´efinie, son nom est enregistr´e avec son corps et la liste de ses arguments dans la liste d’association globale proc´edures_d´efinies. De mˆeme que pour les variables, on recherche donc le corps et la liste d’arguments d’une proc´edure en cherchant sa valeur associ´ee dans la liste des proc´edures d´ej`a d´efinies. # type proc´edure = {param`etres : string list; corps : ordre list};; Le type proc´edure est d´efini. # let proc´edures_d´efinies = ref ([] : (string * proc´edure) list);; proc´edures_d´efinies : (string * proc´edure) list ref = ref [] # let d´efinit_proc´edure (nom, proc as liaison) = proc´edures_d´efinies := liaison :: !proc´edures_d´efinies and d´efinition_de nom_de_proc´edure = assoc nom_de_proc´edure !proc´edures_d´efinies;; d´efinit_proc´edure : string * proc´edure -> unit = d´efinition_de : string -> proc´edure = L’ex´ecution des ordres Comme la fonction d’´evaluation des expressions, la fonction d’ex´ecution des ordres doit g´erer un environnement, puisqu’elle est charg´ee de lier les arguments des proc´edures lorsqu’on les appelle. L’ex´ecution des ordres simples ne change pas : on applique toujours les fonctions de base du crayon. La seule diff´erence consiste `a ´evaluer l’expression argument de l’ordre en appelant valeur_expr dans l’environnement courant. Par exemple, pour Av e, on appellera comme auparavant la fonction avance avec pour argument le flottant obtenu par l’´evaluation de e, c’est-`a-dire valeur_expr env e. L’ordre r´ep`ete prend maintenant une expression en argument : cette expression est ´evalu´ee et retourne une valeur de type nombre. Cependant ce nombre n’est pas forc´ement un entier ; dans le cas o`u c’est une valeur flottante, deux options sont possibles : prendre la d´ecision d’arrondir le nombre `a l’entier le plus proche (r´ep´eter 3.1 fois signifie alors r´ep´eter 3 fois) ; ou bien ´echouer, au pr´etexte qu’on ne peut pas r´ep´eter un nombre flottant de fois une liste d’ordres (r´ep´eter 3.1 fois n’a pas de sens). C’est cette derni`ere solution que nous adoptons. Nous d´efinissons donc une fonction de conversion d’un nombre en une valeur enti`ere qui ´echoue si son argument est flottant. # let valeur_enti`ere = function | Entier i -> i | Flottant f -> failwith "entier attendu";; valeur_enti`ere : nombre -> int = Voyons maintenant le code de la fonction qui ex´ecute les ordres. Nous d´etaillerons ensuite le code qui correspond aux ordres nouveaux. # exception Fin_de_proc´edure;; L’exception Fin_de_proc´edure est d´efinie. # let rec ex´ecute_ordre env = function | Av e -> avance (flottant (valeur_expr env e)) | Re e -> avance (-. (flottant (valeur_expr env e))) | Tg a -> tourne (flottant (valeur_expr env a)) 172 Syntaxe abstraite, syntaxe concr`ete | Td a -> tourne (-. (flottant (valeur_expr env a))) | Lc -> fixe_crayon true | Bc -> fixe_crayon false | Ve -> vide_´ecran () | Rep (n, l) -> for i = 1 to valeur_enti`ere (valeur_expr env n) do do_list (ex´ecute_ordre env) l done | Si (e1, e2, alors, sinon) -> if compare_nombres (valeur_expr env e1, valeur_expr env e2) then do_list (ex´ecute_ordre env) alors else do_list (ex´ecute_ordre env) sinon | Stop -> raise Fin_de_proc´edure | Ex´ecute (nom_de_proc´edure, args) -> let d´efinition = d´efinition_de nom_de_proc´edure in let variables = d´efinition.param`etres and corps = d´efinition.corps in let rec augmente_env = function | [], [] -> env | variable::vars, expr::exprs -> (variable, valeur_expr env expr) :: augmente_env (vars, exprs) | _ -> failwith ("mauvais nombre d’arguments pour " ^ nom_de_proc´edure) in let env_pour_corps = augmente_env (variables, args) in try do_list (ex´ecute_ordre env_pour_corps) corps with Fin_de_proc´edure -> ();; ex´ecute_ordre : (string * nombre) list -> ordre -> unit = L’ordre Si est tr`es simple : si la comparaison des deux expressions renvoie vrai, on ex´ecute la partie alors et dans le cas contraire on ex´ecute la partie sinon. Remarquez que la fonction ex´ecute_ordre est appliqu´ee partiellement `a l’environnement courant : c’est la fonction ainsi obtenue qui est appliqu´ee `a tous les ordres de la liste choisie (do_list (ex´ecute_ordre env) alors). L’ordre Stop est ex´ecut´e en d´eclenchant l’exception Fin_de_proc´edure qui interrompt donc brutalement l’ex´ecution de la liste d’ordres constituant le corps d’une proc´edure. Cette exception est ´evidemment surveill´ee par l’ex´ecution du corps de toute proc´edure ; si elle survient, elle est alors rattrap´ee et la proc´edure est consid´er´ee comme termin´ee : c’est le try . . . with qui apparaˆıt dans la clause concernant Ex´ecute. L’´evaluation d’un ordre Ex´ecute consiste d’abord `a obtenir la d´efinition de la proc´edure, ses variables et son corps. Puis on calcule l’environnement dans lequel le corps de la proc´edure doit ˆetre ´evalu´e ; c’est l’environnement env_pour_corps. Il est obtenu en liant les param`etres de la proc´edure aux valeurs des arguments avec lesquels la proc´edure a ´et´e appel´ee. La fonction locale augmente_env parcourt donc simultan ´ement la liste des param`etres de la proc´edure et la liste des arguments. Si ces listes sont vides (proc´edure sans param`etre ou liste de param`etres compl`etement trait´ee), le nouvel environnement est l’environnement courant env. Sinon, il suffit de calculer la liaison du premier param`etre de la proc´edure, variable, `a la valeur du premier argument, l’expression expr. On ajoute donc la paire (variable, valeur_expr env Ajout des proc´edures 173 expr) `a la liste des autres liaisons qu’on obtient en appelant r´ecursivement la fonction augmente_env sur le reste des param`etres et le reste des valeurs des arguments, les expressions exprs. ´Evidemment, si la liste des param`etres et la liste des expressions ne s’´epuisent pas en mˆeme temps, c’est qu’il y a une erreur sur le nombre d’arguments fournis lors de l’appel de la proc´edure ; on ´echoue alors avec un message d’erreur. Il ne reste plus ensuite qu’`a ex´ecuter la liste des ordres du corps de la proc´edure dans ce nouvel environnement, en surveillant le d´eclenchement de l’exception Stop. Remarquez encore une fois l’application partielle de la fonction ex´ecute_ordre, non pas `a l’environnement courant, mais `a celui ad´equat `a l’ex´ecution du corps de la proc´edure : do_list (ex´ecute_ordre env_pour_corps) corps. L’´evaluation des programmes Les phrases de notre mini-Logo sont soit des d´efinitions de proc´edures, soit des ordres. Un programme est une suite de phrases. # type phrase_logo = | Pour of string * proc´edure | Ordre of ordre;; Le type phrase_logo est d´efini. # type programme_logo = Programme of phrase_logo list;; Le type programme_logo est d´efini. On ex´ecute un ordre en appelant la fonction ex´ecute_ordre dans un environnement initialement vide, tandis qu’une d´efinition se contente d’appeler la fonction d´efinit_proc´edure. # let rec ex´ecute_phrase = function | Ordre ord -> ex´ecute_ordre [] ord | Pour (nom, proc as liaison) -> d´efinit_proc´edure liaison and ex´ecute_programme = function | Programme phs -> do_list ex´ecute_phrase phs;; ex´ecute_phrase : phrase_logo -> unit = ex´ecute_programme : programme_logo -> unit = L’analyseur syntaxique L’analyseur syntaxique est tr`es semblable `a celui de la section 9.6 ; il est seulement un peu plus long. Nous avons regroup´e les clauses analogues, dues aux mots cl´es synonymes, `a l’aide de filtres « ou », par exemple [< ’(Mot "baisse_crayon" | Mot "bc") >] -> Bc. Remarquez aussi que les expressions « alors » et « sinon » de l’alternative si sont des listes d’ordres et que la condition est forc´ement la comparaison avec >= de deux expressions. Un petit d´etail encore : nous avons ajout´e la possibilit´e de taper directement des nombres n´egatifs dans la fonction nombre. En effet, si le nombre commence par un signe -, nous rendons en r´esultat son oppos´e en appelant la primitive Caml correspondant au type du nombre, qui est simplement d´etermin´e par un filtrage explicite. # let rec analyse_programme = function | [< analyse_phrase ph; analyse_programme p >] -> ph :: p | [< ’Symbole ‘.‘ >] -> [] 174 Syntaxe abstraite, syntaxe concr`ete | [< >] -> [] and analyse_phrase = function | [< ’Mot "pour"; ’Mot s; param`etres variables; suite_d’ordres ordres; ’Symbole ‘.‘ >] -> Pour (s, {param`etres = variables; corps = ordres}) | [< ordre ord >] -> Ordre ord and param`etres = function | [< ’Symbole ‘:‘; ’Mot s; param`etres l >] -> s::l | [< >] -> [] and ordre = function | [< ’(Mot "avance" | Mot "av"); expression e >] -> Av e | [< ’(Mot "recule" | Mot "re"); expression e >] -> Re e | [< ’(Mot "droite" | Mot "td"); expression e >] -> Td e | [< ’(Mot "gauche" | Mot "tg"); expression e >] -> Tg e | [< ’(Mot "baisse_crayon" | Mot "bc") >] -> Bc | [< ’(Mot "l`eve_crayon" | Mot "lc") >] -> Lc | [< ’(Mot "vide_´ecran" | Mot "ve") >] -> Ve | [< ’Mot "stop" >] -> Stop | [< ’Mot "si"; expression e1; ’Symbole ‘>‘; ’Symbole ‘=‘; expression e2; liste_d’ordres alors; liste_d’ordres sinon >] -> Si (e1, e2, alors, sinon) | [< ’(Mot "r´ep`ete" | Mot "rep"); expression e; liste_d’ordres l >] -> Rep (e,l) | [< ’Mot f; liste_d’expressions exprs >] -> Ex´ecute (f, exprs) and liste_d’ordres = function | [< ’Symbole ‘[‘; suite_d’ordres l; ’Symbole ‘]‘ >] -> l and suite_d’ordres = function | [< ordre ord; suite_d’ordres l >] -> ord::l | [< >] -> [] and nombre = function | [< ’Symbole ‘-‘; nombre n >] -> begin match n with | Entier i -> Entier (-i) | Flottant f -> Flottant (-. f) end | [< ’Constante_enti`ere i >] -> Entier i | [< ’Constante_flottante f >] -> Flottant f and expression_simple = function | [< nombre n >] -> Constante n | [< ’Symbole ‘:‘; ’Mot var >] -> Variable var | [< ’Symbole ‘(‘; expression e; ’Symbole ‘)‘ >] -> e and expression = function | [< expression_simple e; (reste_de_l’expression e) e’ >] -> e’ Ajout des proc´edures 175 and reste_de_l’expression e = function | [< ’Symbole ‘+‘; expression e2 >] -> Somme (e, e2) | [< ’Symbole ‘*‘; expression e2 >] -> Produit (e, e2) | [< ’Symbole ‘-‘; expression e2 >] -> Diff´erence (e, e2) | [< ’Symbole ‘/‘; expression e2 >] -> Quotient (e, e2) | [< >] -> e and liste_d’expressions = function | [< expression exp; liste_d’expressions l >] -> exp::l | [< >] -> [];; La fonction principale, logo, combine analyse lexicale, analyse syntaxique et ´evaluation pour ex´ecuter un programme lu dans une chaˆıne de caract`eres. # let logo cha^ıne = do_list ex´ecute_phrase (analyse_programme (analyseur_lexical (stream_of_string cha^ıne)));; logo : string -> unit = Voici en guise d’exemple les d´efinitions successives de deux proc´edures, suivies d’une suite d’ordres : # logo "pour carr´e :c r´ep`ete 4 [av :c td 90]. pour multi_carr´e :c :n r´ep`ete :n [carr´e :c td 10]. ve multi_carr´e 80 10 .";; - : unit = () Remarquez que nous devons ´ecrire 10 . avec un blanc entre le z´ero et le point, car sinon l’analyseur lexical croirait avoir affaire `a un nombre flottant. Un peu plus difficile maintenant : une proc´edure r´ecursive spirale, qui s’arrˆete lorsqu’elle a ´et´e appel´ee :n fois et qui fait avancer le crayon de la distance :d en tournant de l’angle :a `a chacune de ses invocations. La proc´edure s’appelle r´ecursivement avec une distance augment´ee de l’argument :i, qui est donc l’incr´ement ajout´e `a la distance que parcourra le crayon `a la prochaine ´etape. # logo "pour spirale :d :a :i :n si :n >= 0 [av :d td :a spirale (:d + :i) :a :i (:n - 1)] [stop].";; - : unit = () 176 Syntaxe abstraite, syntaxe concr`ete # logo "ve spirale 0 179.5 0.5 360 .";; - : unit = () # logo "ve spirale 0 178.5 0.5 360 .";; - : unit = () Cette fonction est ´etonnamment sensible `a la valeur de ses param`etres de d´epart. Les deux dessins ci-dessus correspondent `a une variation d’un degr´e de l’angle, toutes choses restant ´egales par ailleurs. De mˆeme, voyez la diff´erence entre les deux dessins suivants, o`u l’angle de d´epart n’a ´et´e modifi´e que de 0.3 degr´es. # logo "ve spirale 0 79.8 0.4 360 .";; - : unit = () # logo "ve spirale 0 79.5 0.4 360 .";; - : unit = () Nous ´ecrivons maintenant une proc´edure analogue mais qui incr´emente l’angle de vis´ee au lieu de la distance de d´eplacement. # logo "pour spirala :d :a :i :n si :n >= 0 [av :d td :a spirala :d (:a + :i) :i (:n - 1)] [stop].";; - : unit = () On obtient encore une fois des figures tr`es diverses. Ajout des proc´edures 177 # logo "ve spirala 5 0 89.5 1440 .";; - : unit = () # logo "ve spirala 4 0.5 181.5 1500 .";; - : unit = () Au fait, spirale et spirala sont des proc´edures r´ecursives. Mais qu’avons-nous fait pour que les proc´edures de notre mini-Logo puissent ˆetre r´ecursives ? Comment se fait-il que nous soyons capables de les ex´ecuter ? Nous avons vu qu’en Caml cela n’allait pas de soi puisqu’il nous avait fallu pour cela utiliser le mot-cl´e rec. Examinez les fonctions d’´evaluation des ordres et de d´efinition des proc´edures : vous remarquerez qu’une proc´edure est toujours d´efinie sans examen de son corps. Rien ne l’empˆeche donc de faire r´ef´erence `a une autre proc´edure pas encore d´efinie, en particulier elle peut faire r´ef´erence `a elle-mˆeme. Lors de l’ex´ecution, il n’y a pas de probl`eme non plus : on va rechercher la d´efinition de la proc´edure autant de fois que n´ecessaire et cette recherche r´eussit toujours puisque la proc´edure est d´efinie . . .Puisque la r´ecursivit´e est automatique en mini-Logo vous devinez que, `a la diff´erence de Caml, mini-Logo n’ob´eit pas `a la r`egle de port´ee statique. En effet lors de l’ex´ecution du corps d’une proc´edure, quand on rencontre un identificateur, on interroge l’environnement courant : on va donc trouver la valeur actuellement li´ee au nom. Cette liaison a ´et´e ´etablie au cours de l’ex´ecution du programme, elle n’est pas celle qu’on obtiendrait en examinant le texte du programme. Ce type de port´ee des identificateurs se nomme la port´ee dynamique. Il est donc l´egal en mini-Logo d’utiliser une proc´edure avant de l’avoir d´efinie . . . 10 Programmes ind´ependants et modules O`u l’on apprend `a diviser pour r´egner sur ses programmes. usqu’ici, nous avons utilis´e Caml d’une mani`ere enti`erement interactive : l’utilisateur entre une phrase, le syst`eme l’ex´ecute, l’utilisateur entre une deuxi`eme phrase, et ainsi de suite. C’est tr`es pratique pour apprendre le langage et exp´erimenter avec des fonctions, mais malcommode pour construire de v´eritables programmes. Dans ce chapitre, nous allons voir d’autres modes d’utilisation de Caml, qui ´evitent d’entrer sans cesse les mˆemes d´efinitions ; qui autorisent la constructions de programmes ind´ependants, utilisables sans connaissance pr´ealable de Caml ; et enfin, qui procurent un moyen de structurer des gros programmes en modules. 10.1 Chargement de fichiers Si vous avez eu la patience de taper un des gros exemples des chapitres pr´ec´edents, vous vous ˆetes certainement demand´e comment sauvegarder tout ce travail, afin de pouvoir r´eutiliser ult´erieurement les fonctions que vous avez p´eniblement entr´ees. Malheureusement pour vous, c’est impossible dans le syst`eme Caml Light. En revanche, vous auriez pu ´ecrire les exemples sous un ´editeur de textes, enregistrer le texte des programmes dans un fichier, puis charger ce fichier dans le syst`eme interactif. Caml Light fournit pour ce faire la fonction include : appliqu´ee `a un nom de fichier include se charge de lire les phrases Caml contenues dans le fichier et de les ex´ecuter tout comme si vous les aviez tap´ees `a la main. Par exemple, supposons qu’on ait mis dans le fichier facto.ml les lignes suivantes : let rec fact n = if n <= 1 then 1 else n * fact (n - 1);; fact 10;; On lance alors le syst`eme Caml Light, puis on entre : # include "facto.ml";; fact : int -> int = 180 Programmes ind´ependants et modules - : int = 3628800 - : unit = () Comme on le voit sur les r´eponses du syst`eme, fact est d´efinie, puis fact 10 ´evalu´ee, et le r´esultat est affich´e. Notez que le nom de fichier argument de include doit imp´erativement ˆetre mis entre guillemets " . . . " : c’est en fait une chaˆıne de caract`eres comme les autres. De plus, le nom du fichier doit se terminer par l’extension .ml. Si on donne `a include un nom de fichier qui ne se termine pas par .ml, comme dans include "facto", le syst`eme ajoute .ml de lui-mˆeme et charge donc facto.ml. La m´ethode la plus pratique pour construire interactivement des programmes Caml Light est donc de faire tourner en mˆeme temps un ´editeur de textes et le syst`eme Caml Light, dans deux fenˆetres diff´erentes : on ´ecrit son code sous l’´editeur, on l’enregistre, on passe `a Caml Light, on charge le fichier avec include, on lit les messages d’erreur, on repasse sous l’´editeur, on corrige les erreurs, on repasse `a Caml Light, on recharge, etc. 10.2 Programmes ind´ependants Supposons que l’on veuille utiliser r´eguli`erement le syst`eme Logo pr´esent´e dans les deux chapitres pr´ec´edents. On met donc dans le fichier logo.ml toutes les d´efinitions n´ecessaires, du type ´etat du chapitre 8 `a la fonction logo du chapitre 9. Pour travailler en Logo, il faut alors lancer Caml Light, puis entrer : include "logo";; logo "une phrase Logo";; logo "une autre phrase Logo";; ... quit ();; Cela vaut mieux certes que de r´e´ecrire tout le code du syst`eme Logo, mais ce n’est pas encore id´eal : il faut expliquer aux utilisateurs que chaque phrase Logo doit ˆetre pr´ec´ed´ee de logo " et suivie de ";;, ce qui n’a rien de naturel s’ils ne connaissent pas Caml. Il serait bien meilleur d’avoir un programme logo qu’on puisse lancer comme n’importe quel autre programme de la machine et sous lequel on puisse entrer directement les phrases Logo `a ex´ecuter. Pour ce faire, il faut abandonner le syst`eme Caml Light interactif et utiliser le compilateur ind´ependant. Ce dernier transforme un fichier contenant des phrases Caml Light en un programme directement ex´ecutable. Supposons que le fichier hello.ml contienne les trois phrases : let message = "Bonjour tout le monde!";; print_string message;; print_newline ();; Pour compiler ce fichier, il faut lancer le compilateur Caml Light `a partir de l’interpr`ete de commandes de votre machine. Rappelons que l’interpr`ete de commandes est le programme syst`eme qui permet de lancer d’autres programmes en tapant des lignes de commandes. C’est par exemple le programme « Invite de commande » de MS Windows, ou l’un des nombreux « shells » du syst`eme Unix. Sur le Macintosh, le syst`eme standard ne fournit pas d’interpr`ete de commandes. Le compilateur ind´ependant de Caml Light Entr´ees-sorties de base 181 tourne donc sous un interpr`ete de commandes appel´e MPW (Macintosh Programmer’s Workshop), et disponible gratuitement aupr`es d’Apple. Les lecteurs qui disposent d’un Macintosh mais qui n’ont pas MPWne peuvent pas utiliser le compilateur ind´ependant ; la section 10.6 explique comment proc´eder en utilisant seulement le syst`eme interactif. Nous supposons que l’interpr`ete de commandes a le symbole $ pour signe d’invite. Les lignes qui commencent par $ consistent donc en des commandes entr´ees par l’utilisateur. Les autres lignes sont les messages qu’affichent les commandes pendant qu’elles s’ex´ecutent. On compile le fichier hello.ml `a l’aide de la commande $ camlc -o hello hello.ml Cette commande signifie «mettre dans le fichier hello le code compil´e pour les phrases Caml contenues dans le fichier hello.ml ». S’il n’y a pas d’erreurs, elle se d´eroule sans rien afficher. Pour ex´ecuter le code produit, il faut lancer la commande : $ camlrun hello Bonjour tout le monde! Le programme a ex´ecut´e les trois phrases qui le composent, dans l’ordre, puis a rendu la main. Rien d’autre ne s’affiche : contrairement `a ce qui se passe en utilisation interactive, le syst`eme n’´ecrit pas de lui-mˆeme le type et la valeur de chaque phrase. Dans un programme ind´ependant, si l’on veut faire afficher un r´esultat, il faut le faire explicitement en appelant une fonction d’impression comme print_string. Les diff´erences entre l’utilisation interactive et la construction de programmes ind´ependants sont faibles : les deux modes effectuent presque les mˆemes op´erations, mais dans un ordre diff´erent. Voici ce qui se passe quand on charge un fichier interactivement avec include (`a gauche), compar´e avec ce qui se passe quand on compile ce fichier puis qu’on l’ex´ecute (`a droite) : # include "fichier.ml";; lecture de la phrase 1 typage et compilation de la phrase 1 ex´ecution du code de la phrase 1 impression des r´esultats lecture de la phrase 2 typage et compilation de la phrase 2 ex´ecution du code de la phrase 2 impression des r´esultats . . . $ camlc -o fichier fichier.ml lecture de la phrase 1 typage et compilation de la phrase 1 sauvegarde du code de la phrase 1 lecture de la phrase 2 typage et compilation de la phrase 2 sauvegarde du code de la phrase 2 . . . $ camlrun fichier ex´ecution du code de la phrase 1 ex´ecution du code de la phrase 2 . . . 10.3 Entr´ees-sorties de base Reprenons nos tentatives de r´ealisation d’un syst`eme Logo ind´ependant de Caml Light. Il ne suffit pas de compiler avec camlc le fichier logo.ml contenant toutes les d´efinitions du type ´etat du chapitre 8 `a la fonction logo du chapitre 9. Ce fichier ne 182 Programmes ind´ependants et modules contient que des d´efinitions de fonctions ; l’ex´ecution du code compil´e va donc ex´ecuter les d´efinitions de fonction, qui n’ont pas d’effet visible, puis rendre la main. Bref, notre Logo ne fera rien du tout tant qu’on n’y aura pas ajout´e du code pour lire des commandes au clavier et les envoyer `a la fonction d’ex´ecution. Voici un aper¸cu des fonctions d’entr´ee-sortie de la biblioth`eque Caml Light. Pour afficher sur l’´ecran, on dispose des fonctions suivantes : print_string s affiche la chaˆıne s print_char c affiche le caract`ere c print_int n affiche l’entier n print_float f affiche le nombre flottant f print_newline () affiche un retour `a la ligne Pour lire depuis le clavier, on a read_line (), qui lit une ligne au clavier et renvoie la chaˆıne de caract`eres correspondante. Pour lire et ´ecrire sur des fichiers, au lieu d’utiliser l’´ecran et le clavier, il faut utiliser des canaux d’entr´ee (type in_channel) ou de sortie (type out_channel). L’ouverture d’un fichier en ´ecriture, par la fonction open_out, renvoie un canal de sortie sur le fichier indiqu´e. # let canal_sortie = open_out "essai.tmp";; canal_sortie : out_channel = Pour ´ecrire sur un canal de sortie, on dispose des fonctions output_string et output_char. # output_string canal_sortie "Une ligne de texte\n";; - : unit = () Enfin, on ferme le canal de sortie avec close_out. # close_out canal_sortie;; - : unit = () La lecture est tr`es sym´etrique : ouverture d’un canal d’entr´ee avec open_in, lecture ligne `a ligne par input_line ou caract`ere par caract`ere avec input_char, et fermeture par close_in. # let canal_entr´ee = open_in "essai.tmp";; canal_entr´ee : in_channel = # input_char canal_entr´ee;; - : char = ‘U‘ # input_line canal_entr´ee;; - : string = "ne ligne de texte" # input_line canal_entr´ee;; Exception non rattrap´ee: End_of_file Comme on le voit sur le dernier exemple, l’exception End_of_file se d´eclenche lorsqu’on essaye de lire apr`es la fin du fichier. La lecture au clavier et l’affichage `a l’´ecran sont en fait des cas particuliers d’entr´eessorties sur fichiers. Le syst`eme fournit en effet trois canaux pr´ed´efinis : Identificateur Nom savant Reli´e `a . . . std_in entr´ee standard du programme clavier std_out sortie standard du programme ´ecran std_err sortie d’erreur du programme ´ecran Programmes en plusieurs modules 183 Par exemple, print_string s est exactement ´equivalent `a output_string std_out s et de mˆeme read_line () est synonyme de input_line std_in. Une autre mani`ere de lire un fichier caract`ere par caract`ere est de passer par l’interm´ediaire d’un flux. La fonction stream_of_channel renvoie le flux des caract`eres lus depuis un canal d’entr´ee. En particulier, stream_of_channel std_in est le flux des caract`eres tap´es au clavier. C’est cette fonction qui va nous permettre d’appliquer des analyseurs lexicaux non plus `a des chaˆınes de caract`eres, mais directement `a des fichiers. Voici par exemple la boucle d’interaction qu’il faut ajouter `a la fin du fichier logo.ml pour obtenir enfin un syst`eme Logo ind´ependant. let flux_d’entr´ee = stream_of_channel std_in in let flux_lex`emes = analyseur_lexical flux_d’entr´ee in while true do print_string "? "; flush std_out; try ex´ecute_programme(analyse_programme flux_lex`emes) with | Parse_error -> print_string "Erreur de syntaxe"; print_newline () | Failure s -> print_string ("Erreur `a l’ex´ecution: " ^ s); print_newline () done;; Le flush std_out qui suit l’affichage du signe d’invite sert `a garantir que l’invite est bien affich´ee `a l’´ecran avant que l’on ne commence `a lire la prochaine phrase. En effet, les ´ecritures sur des canaux sont « tamponn´ees » (buffered, en anglais) pour plus d’efficacit´e ; autrement dit, le syst`eme accumule en m´emoire un certain nombre d’ordres d’´ecriture et les effectue en bloc plus tard. En cons´equence, ´evaluer print_string "? " ne suffit pas pour assurer qu’un point d’interrogation apparaˆıt sur l’´ecran ; il faut en plus « vider » (to flush, en anglais) explicitement les ´ecritures en attente. C’est le rˆole de la fonction pr´ed´efinie flush ; d’autres fonctions, comme close_out ou print_newline, vident aussi le canal de sortie. Nous pouvons maintenant compiler logo.ml et ex´ecuter le code produit. $ camlc -o logo logo.ml $ camlrun logo Au lancement de logo, toutes les d´efinitions de fonctions sont ´evalu´ees en s´equence, sans effets visibles, puis la phrase ci-dessus est ex´ecut´ee. La boucle infinie while true do . . . affiche un signe d’invite, puis lit une phrase sur le flux des caract`eres entr´es au clavier. Si le premier mot est fin, on sort du programme en rendant imm´ediatement la main `a l’aide de la fonction pr´ed´efinie exit. Sinon, on ex´ecute la phrase lue et on refait un tour de boucle pour lire la suivante. 10.4 Programmes en plusieurs modules Plutˆot que de mettre tout le texte d’un programme dans un seul fichier, il est pr´ef´erable de le d´ecouper en plusieurs petits fichiers, que l’on compile un par un. Non seulement l’´edition et la recompilation sont facilit´ees, mais surtout on s’autorise alors la r´eutilisation de certains morceaux du programme dans d’autres programmes. Par exemple, les fonctions sur le crayon ´electronique (avance, . . . ) sont susceptibles d’ˆetre 184 Programmes ind´ependants et modules utilis´ees dans bien d’autres programmes que notre syst`eme mini-Logo. On appelle mod- ules ces morceaux de programme suffisamment autonomes pour ˆetre ´eventuellement r´eutilis´es plus tard et programmation modulaire le style de programmation consistant `a d´ecouper syst´ematiquement les programmes en modules. Nous allons donc d´ecouper le mini-Logo en cinq modules : crayon le crayon ´electronique : fonctions avance, tourne, . . . langage le langage de commandes : type ordre, fonction ex´ecute_ordre alex l’analyseur lexical : type lex`eme, fonction lire_lex`eme, . . . asynt l’analyseur syntaxique : fonction analyse_programme, . . . logo le programme principal : la boucle d’interaction. `A chaque module correspond un fichier source, qui a le mˆeme nom que le module, avec l’extension .ml. Par exemple, les fonctions du module crayon sont d´efinies dans le module crayon.ml. Le contenu de ces fichiers est r´esum´e figure 10.1. Noms ext´erieurs La figure 10.1 montre que nous avons ajout´e `a chaque module des lignes de la forme #open plus un nom de module. Ces lignes indiquent d’o`u proviennent les noms ext´erieurs qu’on utilise dans le fichier sans les y avoir d´efinis. Grˆace `a ces indications, le compilateur sait o`u aller chercher le type et le code compil´e de ces noms ext´erieurs. Il y a deux mani`eres de faire r´ef´erence `a un identificateur ext´erieur. L’une est d’utiliser des noms « qualifi´es », de la forme : nom du module d’origine, suivi de deux caract`eres _ (soulign´e), suivi du nom de l’identificateur. Ainsi, asynt__analyse_programme signifie « l’identificateur analyse_programme d´efini dans le module asynt ». L’autre mani`ere d’acc´eder `a des identificateurs ext´erieurs est d’introduire des directives #open "module". Cette directive indique au compilateur qu’on veut « ouvrir » (to open, en anglais) le module donn´e en argument. Plus pr´ecis´ement, cette directive dit que si l’on rencontre un identificateur non qualifi´e qui n’est pas d´efini dans le fichier en cours de compilation, il faut le chercher dans le module argument de #open. Par exemple, dans le fichier asynt.ml, apr`es la ligne #open "langage";; on peut faire r´ef´erence au type ordre et `a ses constructeurs par des identificateurs simples (Av, Re, . . . ). Sans le #open, il aurait fallu utiliser des noms qualifi´es (langage__Av, . . . ). Plusieurs directives #open dans un fichier donnent ainsi au compilateur une liste de modules o`u aller chercher les identificateurs externes. Le choix entre ces deux mani`eres de faire r´ef´erence `a un nom ext´erieur est une pure question de style : l’emploi de #open donne des programmes plus compacts et permet de renommer les modules plus facilement ; l’emploi de noms qualifi´es montre plus clairement la structure modulaire du programme. La biblioth`eque de modules du syst`eme Il n’y a pas que les programmes de l’utilisateur `a ˆetre d´ecoup´es en modules : la biblioth` eque de fonctions pr´ed´efinies du syst`eme Caml Light se pr´esente elle aussi sous la Programmes en plusieurs modules 185 Fichier crayon.ml #open "graphics";; let round x = ... ;; type ´etat = ... ;; let crayon = ... ;; let avance d = ... ;; let pi_sur_180 = ... ;; let tourne angle = ... ;; let avance d = ... ;; let couleur_du_trac´e = ... ;; let couleur_du_fond = ... ;; let z´ero_x = ... ;; let z´ero_y = ... ;; let vide_´ecran () = ... ;; Fichier langage.ml #open "crayon";; type nombre = ...;; let flottant = ... ;; type expression = ... ;; let ajoute_nombres = ... ;; let soustrait_nombres = ... ;; let multiplie_nombres = ... ;; let divise_nombres = ... ;; let compare_nombres = ... ;; let rec valeur_expr env = ... ;; type ordre = ... ;; type proc´edure = ...;; let proc´edures_d´efinies = ... ;; let d´efinit_proc´edure = ... and d´efinition_de = ... ;; let valeur_enti`ere = ... ;; let rec ex´ecute_ordre env = ...;; type phrase_logo = ... ;; type programme_logo = ... ;; let ex´ecute_phrase = ... let ex´ecute_programme = ... ;; Fichier alex.ml type lex`eme = ... ;; let rec saute_blancs = ... ;; let rec lire_entier = ... ;; let rec lire_d´ecimales = ... ;; let rec lire_mot = ... ;; let lire_lex`eme = ... ;; let rec analyseur_lexical = ...;; Fichier asynt.ml #open "langage";; #open "alex";; let rec analyse_programme = ... and analyse_phrase = ... and param`etres = ... and ordre = ... and liste_d’ordres = ... and suite_d’ordres = ... and nombre = ... and expression_simple = ... and expression = ... and reste_de_l’expression = ... and liste_d’expressions = ... ;; Fichier logo.ml #open "langage";; #open "alex";; #open "asynt";; let flux_d’entr´ee = ... in let flux_lex`emes = ... in while true do ... done;; Figure 10.1: D´ecoupage en modules de l’interpr`ete mini-Logo 186 Programmes ind´ependants et modules forme d’un certain nombre de modules. Par exemple, la fonction sub_string provient du module de biblioth`eque string ; de mˆeme, des op´erateurs comme + et +. ne sont pas enti`erement pr´ed´efinis dans le syst`eme, mais proviennent de modules de biblioth`eque (les modules int et float, respectivement). Certains de ces modules de biblioth`eque (comme int, float et string) sont implicitement « ouverts » au lancement du compilateur. Tout se passe comme si on avait mis au d´ebut de tous les fichiers : #open "int";; #open "float";; #open "string";; C’est ce qui explique qu’on r´ef´erence directement sub_string dans n’importe quel programme, sans mettre au pr´ealable #open "string" ni devoir utiliser la forme compl`etement qualifi´ee string__sub_string. D’autres modules de biblioth`eque, d’un emploi moins fr´equent, ne sont pas « ouverts » automatiquement au d´ebut de chaque compilation. C’est le cas par exemple du module graphics fournissant les commandes graphiques de base. Il faut donc mettre #open "graphics" au d´ebut du fichier crayon.ml, qui fait r´ef´erence `a ces commandes graphiques. Compilation s´epar´ee Les modules composant un programme se compilent un par un `a l’aide de la commande camlc -c. $ camlc -c crayon.ml $ camlc -c langage.ml $ camlc -c alex.ml $ camlc -c asynt.ml $ camlc -c logo.ml L’option -c indique au compilateur qu’il ne faut pas essayer de produire un fichier de code ex´ecutable. En d’autres termes, cette option pr´evient le compilateur que le fichier donn´e en argument n’est pas un programme complet, mais seulement un morceau de programme. L’ex´ecution de la commande camlc -c crayon.ml produit deux fichiers : • le fichier crayon.zo, qu’on appelle « fichier de code objet » ; il contient du code compil´e pas encore ex´ecutable, car faisant r´ef´erence `a des identificateurs ext´erieurs ; • le fichier crayon.zi, qu’on appelle « fichier d’interface compil´ee » ; il contient des informations de typage sur les objets d´eclar´es dans le module crayon : types des identificateurs d´efinis, noms des types concrets d´eclar´es avec leurs constructeurs, etc. Le fichier d’interface compil´ee crayon.zi sert pour la compilation des modules qui utilisent le module crayon : quand on compile un module contenant #open "crayon" ou un nom qualifi´e de la forme crayon__. . . , le compilateur lit le fichier crayon.zi et y trouve toutes les informations de typage dont il a besoin. Ce comportement introduit une contrainte sur l’ordre dans lequel on compile les modules : lorsqu’on compile un module, il faut avoir compil´e au pr´ealable tous les modules qu’il utilise, ayant ainsi produit tous les fichiers .zi n´ecessaires `a sa compilation. Dans le cas du mini-Logo, ces contraintes se r´esument par le sch´ema suivant (une fl`eche de A vers B signifie que A utilise B et donc que B doit ˆetre compil´e avant A). Interfaces de modules 187 logo.ml langage.ml asynt.ml alex.ml crayon.ml La s´equence de compilation donn´ee ci-dessus v´erifie toutes les contraintes. On a aussi la libert´e de compiler alex.ml plus tˆot, avant crayon ou langage. ´E dition de liens Lorsque tous les modules composant le programme ont ´et´e compil´es, il faut lier ensemble leurs fichiers de code objet, obtenant ainsi un fichier ex´ecutable par camlrun. Cette op´eration s’appelle l’´edition de liens et s’effectue en appelant camlc avec la liste des fichiers en .zo `a lier ensemble. $ camlc -o logo crayon.zo langage.zo alex.zo asynt.zo logo.zo Comme pour un programme mono-fichier, l’option -o sert `a donner le nom du fichier ex´ecutable `a produire. L’ordre des fichiers .zo sur la ligne a son importance : il doit respecter la mˆeme contrainte que pour l’ordre des compilations, `a savoir qu’un module doit apparaˆıtre avant les modules qui l’utilisent. 10.5 Interfaces de modules Souvent, un module contient des d´efinitions `a usage interne, qui ne sont pas cens´ees ˆetre employ´ees `a l’ext´erieur du module. Dans le module crayon, par exemple, l’enregistrement `a champs mutables qui contient l’´etat courant de la tortue n’est pas cens´e ˆetre modifi´e directement par les utilisateurs de ce module ; les utilisateurs sont suppos´es passer par l’interm´ediaire des fonctions avance, tourne, . . . On peut s’imposer de respecter cette convention soi-mˆeme ; mais le syst`eme est capable de la garantir, si on lui demande explicitement de « cacher » certains des identificateurs d´efinis par le module, ce qui les rend ainsi inaccessibles depuis l’ext´erieur du module. Pour ce faire, il faut ´ecrire une interface au module. L’interface d’un module contient des d´eclarations pour tous les identificateurs du module que l’on veut rendre visibles de l’ext´erieur ; les identificateurs d´efinis dans le module mais non d´eclar´es dans l’interface seront automatiquement cach´es. L’interface d’un module r´eside dans un fichier ayant le mˆeme nom que le module mais avec l’extension .mli. Par opposition, le fichier avec l’extension .ml qui contient les d´efinitions du module s’appelle l’impl´ementation du module. Par exemple, voici le fichier d’interface du module crayon : value vide_´ecran: unit -> unit and fixe_crayon: bool -> unit and tourne: float -> unit and avance: float -> unit;; Comme on le voit, les d´eclarations d’identificateurs sont introduites par le mot-cl´e value et consistent en le nom de l’identificateur suivi de son type. Les interfaces peuvent aussi 188 Programmes ind´ependants et modules contenir des d´efinitions de types et d’exceptions. Par exemple, l’interface du module alex rend public le type des lex`emes, en plus de la fonction d’analyse lexicale. Fichier alex.mli type lex`eme = | Mot of string | Symbole of char | Entier of int | Flottant of float;; value analyseur_lexical: char stream -> lex`eme stream;; On trouvera en figure 10.2 la nouvelle structure du mini-Logo, une fois qu’on a ajout´e des interfaces `a tous les modules (sauf le module principal logo, qui ne d´efinit rien de toute fa¸con). Remarquez que si un type est d´efini dans l’interface d’un module, il est automatiquement d´efini dans l’impl´ementation du module ; il ne faut donc pas recopier sa d´efinition dans cette impl´ementation. Compilation des interfaces Les fichiers d’interface se compilent exactement comme les fichiers d’impl´ementation, par la commande camlc -c. Exemple : $ camlc -c crayon.mli L’ex´ecution de cette commande cr´ee un fichier crayon.zi contenant les d´eclarations de crayon.mli sous une forme compil´ee. Comme dans le cas des modules sans interface (section 10.4), le fichier crayon.zi est consult´e par le compilateur lors de la compilation des modules qui font r´ef´erence au module crayon. De plus, lorsqu’on compile l’impl´ementation crayon.ml, $ camlc -c crayon.ml le compilateur v´erifie la coh´erence de l’impl´ementation avec l’interface compil´ee crayon.zi, c’est-`a-dire qu’il v´erifie que tous les identificateurs d´eclar´es dans l’interface sont bien d´efinis dans l’impl´ementation et qu’ils ont bien le type annonc´e dans l’interface. C’est en cela que la compilation d’un module avec interface explicite diff`ere de la compilation d’un module sans interface : si l’interface .mli existe alors le .zi est construit par compilation du .mli et la compilation de l’impl´ementation .ml consulte le .zi pour v´erifier la coh´erence ; si l’interface .mli n’existe pas, alors la compilation de l’impl´ementation .ml cr´e´e un .zi qui rend public tout ce que l’impl´ementation .ml d´efinit. Il en d´ecoule deux contraintes sur l’ordre dans lequel on effectue les compilations : d’une part, l’interface explicite mod.mli doit ˆetre compil´ee avant tous les fichiers (.ml et .mli) qui font r´ef´erence au module mod ; d’autre part, l’interface explicite mod.mli doit ˆetre compil´ee avant l’impl´ementation mod.ml. Dans le cas du mini-Logo, il en d´ecoule les contraintes suivantes : crayon.ml langage.ml asynt.ml logo.ml alex.ml crayon.mli langage.mli asynt.mli alex.mli Interfaces de modules 189 Fichier crayon.mli value vide_´ecran: unit -> unit and fixe_crayon: bool -> unit and tourne: float -> unit and avance: float -> unit;; Fichier crayon.ml #open "graphics";; let round x = ... ;; type ´etat = ... ;; let crayon = ... ;; let avance d = ... ;; let pi_sur_180 = ... ;; let tourne angle = ... ;; let avance d = ... ;; let couleur_du_trac´e = ... ;; let couleur_du_fond = ... ;; let z´ero_x = ... ;; let z´ero_y = ... ;; let vide_´ecran () = ... ;; Fichier langage.mli type nombre = ... ;; type expression = ... ;; type ordre = ... ;; type proc´edure = ...;; type phrase_logo = ... ;; type programme_logo = ... ;; value ex´ecute_phrase: phrase_logo -> unit and ex´ecute_programme: programme_logo -> unit;; Fichier langage.ml #open "crayon";; let flottant = ... ;; let ajoute_nombres = ... ;; (* ... *) let rec valeur_expr env = ... ;; let proc´edures_d´efinies = ... ;; let d´efinit_proc´edure = ... and d´efinition_de = ... ;; let rec ex´ecute_ordre env = ...;; let ex´ecute_phrase = ... let ex´ecute_programme = ... ;; Fichier alex.mli type lex`eme = ... ;; value analyseur_lexical: char stream -> lex`eme stream;; Fichier alex.ml let rec saute_blancs = ... ;; let rec lire_entier = ... ;; let rec lire_d´ecimales = ... ;; let rec lire_mot position = ...;; let lire_lex`eme = ... ;; let rec analyseur_lexical = ...;; Fichier asynt.mli value analyse_phrase: alex__lex`eme stream -> langage__phrase_logo and analyse_programme: alex__lex`eme stream -> langage__programme_logo;; Fichier asynt.ml #open "langage";; #open "alex";; let rec analyse_programme = ... and analyse_phrase = ... and param`etres = ... and ordre = ... and liste_d’ordres = ... and suite_d’ordres = ... and nombre = ... and expression_simple = ... and expression = ... and reste_de_l’expression = ... and liste_d’expressions = ... ;; Fichier logo.ml #open "langage";; #open "alex";; #open "asynt";; let flux_d’entr´ee = ... in let flux_lex`emes = ... in while true do ... done;; Figure 10.2: Ajout d’interfaces explicites aux modules de l’interpr`ete mini-Logo 190 Programmes ind´ependants et modules Remarquez que les fichiers d’impl´ementation (.ml) sont compilables dans n’importe quel ordre : si un module A utilise un module B, on peut tr`es bien compiler l’impl´ementation de A avant l’impl´ementation de B ; il suffit que l’interface de B ait d´ej`a ´et´e compil´ee. C’est le cas dans la s´equence de compilation ci-dessous. $ camlc -c langage.mli $ camlc -c crayon.mli $ camlc -c langage.ml $ camlc -c alex.mli $ camlc -c asynt.mli $ camlc -c logo.ml $ camlc -c asynt.ml $ camlc -c crayon.ml $ camlc -c alex.ml On a choisi d’´ecrire et de compiler d’abord les impl´ementations de langage et de logo, qui repr´esentent le coeur du syst`eme, et de repousser `a plus tard l’´ecriture de asynt.ml, alex.ml et crayon.ml. Plus g´en´eralement, l’introduction d’interfaces explicites pour les modules permet de se lib´erer du style d’´ecriture des programmes strictement ascendant (bottom-up, en anglais) que nous avons utilis´e jusqu’ici. Par la suite, nous utiliserons des interfaces `a chaque fois que nous avons besoin d’un module de fonctions auxiliaires, dont nous pr´ef´erons cependant repousser l’impl´ementation `a plus tard. 10.6 Compilations interactives Le compilateur ind´ependant n’est pas le seul moyen de compiler des fichiers : le syst`eme interactif fournit lui aussi des commandes pour compiler des fichiers (compile) et charger et ex´ecuter du code compil´e (load_object). La commande # compile "monfichier.ml";; compile le fichier monfichier.ml exactement comme le ferait l’appel camlc -c monfichier.ml du compilateur ind´ependant. La fonction compile accepte ´egalement des fichiers d’interface (.mli) en plus des fichiers d’impl´ementation (.ml). La commande # load_object "monfichier.zo";; charge en m´emoire le code compil´e qui se trouve dans le fichier monfichier.zo et l’ex´ecute phrase par phrase. Les deux fonctions compile et load_object permettent donc de compiler et d’ex´ecuter un programme ´ecrit sous forme modulaire `a partir du syst`eme interactif, ce qui est pr´ecieux pour les utilisateurs de Macintosh ne disposant pas du compilateur ind´ependant. Par exemple, le programme logo se compile et s’ex´ecute de la mani`ere suivante : # compile "langage.mli";; # compile "crayon.mli";; # compile "langage.ml";; # compile "alex.mli";; # compile "asynt.mli";; # compile "logo.ml";; # compile "asynt.ml";; # compile "crayon.ml";; # compile "alex.ml";; Compilations interactives 191 # load_object "crayon.zo";; # load_object "langage.zo";; # load_object "alex.zo";; # load_object "asynt.zo";; # load_object "logo.zo";; Mˆeme lorsqu’on dispose du compilateur ind´ependant, charger du code compil´e dans le syst`eme interactif avec la fonction load_object est souvent tr`es utile pour essayer et mettre au point les fonctions d’un programme. Par exemple, pour tester l’analyseur syntaxique du mini-Logo, il suffit de lancer le syst`eme interactif et de faire : # load_object "alex.zo";; # load_object "asynt.zo";; # asynt__analyse_phrase (alex__analyseur_lexical (stream_of_string "ve av 15 ."));; L’utilisation interactive de modules compil´es ind´ependamment est un peu d´elicate. En particulier, il faut charger les fichiers de code compil´e dans le bon ordre (un module doit ˆetre charg´e avant les modules qui l’utilisent). Ces points sont expliqu´es en d´etails dans le chapitre 4 du Manuel de r´ef´erence du langage Caml. 11 Interfaces graphiques O`u Caml attrape des boutons et ouvre les fenˆetres. umains et machines n’interagissent pas uniquement par l’interm´ediaire d’un clavier et d’un ´ecran de texte. Nous montrons dans ce chapitre comment doter les programmes Caml de jolies interfaces homme-machine graphiques, avec menus, boutons, barres de d´efilement, etc. Nous utiliserons pour cela la biblioth`eque CamlTk, qui fournit une interface simple avec la boˆıte `a outils (toolkit) Tk. Les programmes de ce chapitre peuvent ˆetre ex´ecut´es avec camltktop, une version du syst`eme interactif Caml qui int`egre la biblioth`eque CamlTk ; elle se lance par la commande camllight camltktop. 11.1 Structure d’une interface graphique Une interface utilisateur r´ealis´ee avec CamlTk est constitu´ee d’un certain nombre de composants de base (en anglais, widgets), tels que boutons, menus, zones d’entr´ee de texte, etc. Le programme cr´ee les composants dont il a besoin pour interagir avec l’utilisateur, et les place dans une ou plusieurs fenˆetres `a l’´ecran. Il associe des fonctions Caml `a certains types d’´ev´enement, tels que un clic de la souris dans un bouton, la s´election d’une entr´ee de menu, ou l’appui d’une touche du clavier. Finalement, il appelle la boucle d’interaction de CamlTk, qui affiche tous les composants `a l’´ecran, g`ere l’interaction avec l’utilisateur, et appelle les fonctions Caml correspondant aux ´ev´enements qui int´eressent le programme. Commen¸cons par un exemple tr`es simple : une interface r´eduite `a un seul bouton, qui affiche le message Bonjour! lorsque l’utilisateur clique sur le bouton. # #open "tk";; let fen^etre_principale = openTk () in let action () = print_string "Bonjour!"; print_newline () in let bouton = button__create fen^etre_principale [Text "Pressez-moi"; Command action] in pack [bouton] []; 194 Interfaces graphiques mainLoop ();; Comme tous les programmes CamlTk, celui-ci commence par un appel `a openTk. Cette fonction initialise la biblioth`eque CamlTk, et renvoie un composant qui repr´esente la fenˆetre principale de l’application. Ensuite, le programme cr´ee le bouton qui constitue l’essentiel de son interface, `a l’aide de la fonction button__create. Comme toutes les fonctions de cr´eation de composants CamlTk, button__create prend deux arguments : un composant « p`ere » et une liste d’options qui d´eterminent l’apparence et le comportement du bouton. Le composant « p`ere » est le composant `a l’int´erieur duquel le bouton doit apparaˆıtre ; ici, c’est la fenˆetre principale de l’application, telle que renvoy´ee par openTk (). Quant aux options, nous en donnons ici deux : Text "Pressez-moi", qui indique que le bouton doit porter l’´etiquette «Pressez-moi », et Command action, qui associe la fonction Caml action aux clics sur le bouton. Le programme appelle ensuite la fonction pack (« emballer », en anglais) pour effectuer le placement des composants dans les fenˆetres. La fonction pack prend une liste de composants et une liste d’options expliquant comment placer les composants les uns par rapport aux autres (l’un au-dessus de l’autre ou l’un `a cˆot´e de l’autre, avec ou sans espace entre les composants, etc.). Ici, le placement est tr`es simple, puisqu’il n’y a qu’un seul composant dans notre interface. Enfin, la fonction mainLoop est appel´ee. Celle-ci ouvre la fenˆetre `a l’´ecran, affiche le bouton dedans, et appelle la fonction action `a chaque fois que l’utilisateur clique sur le bouton. La fonction mainLoop ne rend la main que lorsque l’utilisateur a ferm´e la fenˆetre CamlTk, ou bien interrompu le programme. 11.2 Relier des composants entre eux Passons maintenant `a un exemple plus int´eressant, qui introduit deux nouveaux types de composants (les glissi`eres et les cadres), et montre comment coupler entre eux les ´etats de plusieurs composants. Il s’agit d’un programme permettant de choisir des couleurs `a l’´ecran et jouant sur les intensit´es des trois couleurs primaires (rouge, vert, bleu). Trois glissi`eres contrˆolent ces intensit´es ; d`es que l’utilisateur d´eplace l’une des glissi`eres, la couleur correspondante est affich´ee dans le rectangle en bas de la fenˆetre. # let fen^etre_principale = openTk ();; let cr´eer_glissi`ere nom = scale__create fen^etre_principale [Label nom; From 0.0; To 255.0; Length(Centimeters 10.0); Orient Horizontal];; let rouge = cr´eer_glissi`ere "Rouge" and vert = cr´eer_glissi`ere "Vert" and bleu = cr´eer_glissi`ere "Bleu" and ´echantillon = frame__create fen^etre_principale Relier des composants entre eux 195 [Height(Centimeters 1.5); Width(Centimeters 6.0)] and quitter = button__create fen^etre_principale [Text "Quitter"; Command closeTk];; Nous commen¸cons par cr´eer trois glissi`eres (scale) pour chacune des couleurs primaires rouge, vert et bleu. Ces glissi`eres prennent des valeurs entre 0 et 255 (From 0.0; To 255.0), ont une longueur de 10 cm (Length(Centimeters 10.0)) et sont orient´ees dans le sens horizontal (Orient Horizontal). Pour afficher la couleur, nous cr´eons ´egalement un cadre (frame), qui est une zone inactive de 6 cm sur 1,5 cm (Height(Centimeters 1.5); Width(Centimeters 6.0)). Enfin, le dernier composant du programme est un bouton ´etiquet´e «Quitter » dont l’action (closeTk) est de fermer la fenˆetre de CamlTk, terminant ainsi la boucle d’interaction mainLoop. # let rafra^ıchir_couleur x = let r = int_of_float(scale__get rouge) and v = int_of_float(scale__get vert) and b = int_of_float(scale__get bleu) in let couleur = printf__sprintf "#%02x%02x%02x" r v b in frame__configure ´echantillon [Background (NamedColor couleur)];; La fonction rafra^ıchir_couleur est le coeur du programme : elle change la couleur d’´echantillon pour refl´eter l’´etat courant des trois glissi`eres. Elle lit la valeur num´erique courante des trois glissi`eres `a l’aide de scale__get, puis construit le nom CamlTk de la couleur correspondante. Ce nom est de la forme #rrvvbb, o`u rr, vv et bb sont les intensit´es de rouge, de vert et de bleu, exprim´ees sous forme d’un nombre hexad´ecimal `a deux chiffres. Le nom de la couleur est construit `a l’aide de la fonction sprintf du module printf, qui est un puissant outil d’impression format´ee. La fonction sprintf prend en argument une chaˆıne de caract`ere, le « format », et un certain nombre d’entiers ou de chaˆınes de caract`eres ; elle affiche la chaˆıne de caract`eres, en rempla¸cant les s´equences de la forme % plus une lettre par le prochain argument. Par exemple, sprintf "%d + %d" 1 2 renvoie la chaˆıne "1 + 2". Les chiffres et la lettre suivant % indiquent le type de l’argument `a afficher et le format d’affichage `a utiliser. De nombreux formats sont disponibles. Dans l’exemple ci-dessus, nous avons utilis´e %d, qui convertit un entier en d´ecimal. Dans la fonction rafra^ıchir_couleur, nous utilisons %02x, qui convertit un entier en hexad´ecimal (x), sur deux chiffres (2), en compl´etant `a gauche avec des z´eros si n´ecessaire (0). Enfin, nous changeons la couleur de fond du cadre ´echantillon `a l’aide de la fonction frame__configure. De mani`ere g´en´erale, toutes les options qui peuvent ˆetre sp´ecifi´ees au moment o`u l’on cr´ee un composant (en second argument de com- posant__create) peuvent aussi ˆetre sp´ecifi´ees ou modifi´ees plus tard via la fonction composant__configure. Mettant imm´ediatement ce principe en pratique, nous utilison scale__configure pour associer la fonction rafra^ıchir_couleur au d´eplacement de chacune des glissi`eres : # scale__configure rouge [ScaleCommand rafra^ıchir_couleur]; scale__configure vert [ScaleCommand rafra^ıchir_couleur]; scale__configure bleu [ScaleCommand rafra^ıchir_couleur]; pack [rouge; vert; bleu] [Side Side_Top]; 196 Interfaces graphiques pack [quitter] [Side Side_Bottom]; pack [´echantillon] [Side Side_Bottom; PadY(Millimeters 2.0)]; mainLoop ();; Comme dans le premier exemple, nous pla¸cons finalement tous les composants `a l’aide de pack, puis lan¸cons l’interaction avec l’utilisateur en appelant mainLoop (). Le placement se fait en trois temps : d’abord, les trois glissi`eres en haut de la fenˆetre (Side Side_Top) ; puis le bouton «Quitter » en bas (Side Side_Bottom) ; enfin, le cadre ´echantillon en bas de l’espace restant libre (Side Side_Bottom), et avec une marge de 2 mm en haut et en bas (PadY(Millimeters 2.0)) pour que ce soit plus joli. 11.3 Un convertisseur de devises Notre prochain exemple est d’actualit´e : il s’agit d’une calculatrice de conversion francs-euros. Elle introduit plusieurs nouveaux types de composant : les zones d’entr´ee, dans lesquelles l’utilisateur peut taper et ´editer un texte ; les ´etiquettes ; les menus d´eroulants. Elle illustre ´egalement le m´ecanisme g´en´eral de liaison de CamlTk, qui permet d’associer une action Caml `a presque n’importe quel type d’´ev´enement (appui de touche, clic de souris, mouvement de la souris, etc.). La calculatrice se compose de deux zones d’entr´ee, l’une pour les francs, l’autre pour les euros. D`es que l’utilisateur modifie le montant figurant dans l’une des zones, ce montant est converti dans l’autre monnaie et affich´e dans l’autre zone. Nous commen¸cons par la fonction centrale de l’application, qui assure cette mise `a jour automatique d’une zone d’entr´ee (le param`etre dest) lorsque l’autre zone (le param`etre source) change. # let synchronise_zones source dest taux_source taux_dest = function infos -> try let montant_source = float_of_string (entry__get source) in let montant_dest = montant_source *. !taux_source /. !taux_dest in entry__delete_range dest (At 0) End; entry__insert dest (At 0) (printf__sprintf "%.2f" montant_dest) with Failure _ -> entry__delete_range dest (At 0) End; entry__insert dest (At 0) "erreur";; La fonction entry__get renvoie le texte qui figure actuellement dans un composant « zone d’entr´ee » (entry). Ce texte peut ˆetre modifi´e par le programme `a l’aide de entry__delete_range, qui efface un intervalle de caract`eres (ici, depuis At 0, le premier caract`ere, jusqu’`a End, le dernier caract`ere, effa¸cant ainsi tout le texte), et entry__insert, qui ins`ere la chaˆıne donn´ee en argument `a la position sp´ecifi´ee (ici, At 0, c’est-`a-dire le d´ebut de la zone). # let fp = openTk ();; let ligne1 = frame__create fp [] and ligne2 = frame__create fp [];; Un convertisseur de devises 197 let ´etiq1 = label__create ligne1 [Text "Francs:"] and entr´ee1 = entry__create ligne1 [TextWidth 10; Relief Sunken] and ´etiq2 = label__create ligne2 [Text "Euros:"] and entr´ee2 = entry__create ligne2 [TextWidth 10; Relief Sunken];; Nous cr´eons maintenant les quatre composants de notre application : deux zones d’entr´ee et deux « ´etiquettes » (label ), qui sont des zones passives affichant un texte. Pour permettre l’arrangement « en carr´e » de ces composants (voir figure ci-dessous), nous cr´eons ´egalement deux cadres, ligne1 et ligne2, qui servent de composants p`ere `a ´etiq1 et entr´ee1 pour l’un, et ´etiq2 et entr´ee2 pour l’autre. # let taux1 = ref 1.0 (* francs pour 1 franc *) and taux2 = ref 6.55957 (* francs pour 1 euro *);; bind entr´ee1 [[], KeyRelease] (BindSet([], synchronise_zones entr´ee1 entr´ee2 taux1 taux2));; bind entr´ee2 [[], KeyRelease] (BindSet([], synchronise_zones entr´ee2 entr´ee1 taux2 taux1));; Ci-dessus, nous associons la fonction synchronise_zones `a l’´ev´enement « relˆacher une touche enfonc´ee » (KeyRelease) dans les deux zones d’entr´ee. La fonction bind g`ere les associations de fonctions Caml `a des ´ev´enements : elle prend en arguments le composant concern´e, une liste d’´ev´enements et de modificateurs (ici, [[], KeyRelease], signifiant la relˆache d’une touche sans modificateurs particuliers), et une action `a effectuer (ici, BindSet, signifiant la d´efinition d’une nouvelle liaison ; on dispose aussi de BindRemove pour enlever toutes les liaisons, et de BindExtend pour rajouter une liaison). Nous avons choisi de li´e l’´ev´enement KeyRelease plutˆot que KeyPressed (appui d’une touche), afin que la fonction Caml soit appel´ee apr`es l’insertion du caract`ere frapp´e dans la zone d’entr´ee, et non pas avant, ce qui produirait des r´esultats visuellement faux. # pack [´etiq1] [Side Side_Left]; pack [entr´ee1] [Side Side_Right]; pack [´etiq2] [Side Side_Left]; pack [entr´ee2] [Side Side_Right]; pack [ligne1; ligne2] [Side Side_Top; Fill Fill_X]; mainLoop ();; ´etiq1 entr´ee1 ligne1 ´etiq2 entr´ee2 ligne2 Nous appelons pack cinq fois pour r´ealiser l’arrangement en carr´e des composants d´ecrit par la figure ci-dessus, puis lan¸cons la boucle d’interaction. Choix des devises par un menu Il n’est pas difficile d’´etendre notre convertisseur `a d’autres devises que le franc et l’euro. Pour ce faire, nous ajoutons deux menus d´eroulants permettant de choisir les devises « source » et « cible » de la conversion. # let barre_de_menus = frame__create fp [Relief Raised; BorderWidth (Pixels 2)];; let bouton_source = menubutton__create barre_de_menus [Text "Source"; UnderlinedChar 0] and bouton_cible = 198 Interfaces graphiques menubutton__create barre_de_menus [Text "Cible"; UnderlinedChar 0];; let source = menu__create bouton_source [] and cible = menu__create bouton_cible [];; menubutton__configure bouton_source [Menu source]; menubutton__configure bouton_cible [Menu cible]; pack [bouton_source; bouton_cible] [Side Side_Left];; La barre de menus se compose d’un cadre (barre_de_menus), de deux « boutons `a menus » (bouton_source et bouton_cible) dont l’effet est de d´erouler les menus correspondants, et enfin de deux composants de type «menu » (source et cible) qui contiennent les entr´ees des menus. Les menus sont pour l’instant vides ; nous allons les remplir dans le code qui suit. # let liste_de_devises = [ "Dollars US", 5.9389; "Dollars canadiens", 3.933046; "Euros", 6.55957; "Francs", 1.0; "Francs belges", 0.162531; "Francs suisses", 4.116; "Lires", 0.00338617; "Livres", 9.552; "Marks", 3.354; "Pesetas", 0.0394061; "Yens", 0.05011 ];; do_list (function (nom, taux) -> menu__add_command source [Label nom; Command(function () -> label__configure ´etiq1 [Text(nom ^ ":")]; taux1 := taux; synchronise_zones entr´ee1 entr´ee2 taux1 taux2 ())]) liste_de_devises; do_list (function (nom, taux) -> menu__add_command cible [Label nom; Command(function () -> label__configure ´etiq2 [Text(nom ^ ":")]; taux2 := taux; synchronise_zones entr´ee2 entr´ee1 taux2 taux1 ())]) liste_de_devises;; Pour chaque devise, nous ajoutons une entr´ee dans le menu « Source » et une entr´ee dans le menu «Cible ». Les actions associ ´ees `a ces entr´ees de menus changent le nom de la devise dans l’´etiquette correspondante, puis ajustent le taux de conversion (taux1 ou taux2 respectivement), et enfin appellent synchronize_zones pour mettre `a jour les montants affich´es dans les zones d’entr´ee. Les taux de conversion utilisent le franc comme devise de r´ef´erence. # pack [barre_de_menus] [Side Side_Top; Fill Fill_X]; pack [ligne1; ligne2] [Side Side_Top; Fill Fill_X]; mainLoop ();; Le jeu du taquin 199 11.4 Le jeu du taquin Pour conclure ce chapitre, nous ´ecrivons en CamlTk un jeu de taquin. Le taquin est un puzzle invent´e en 1879 par Sam Lloyd et constitu´e de pi`eces rectangulaires. L’une des pi`eces manque, ce qui permet de d´eplacer les autres pi`eces en les faisant glisser dans l’espace ainsi m´enag´e. Le but du jeu est bien sˆur de reconstituer l’image en faisant ainsi glisser les pi`eces. Ce petit jeu est l’occasion d’introduire un nouvel outil de placement des composants : la toile (canvas). Jusqu’ici, tous nos placements de composants s’effectuaient par la fonction pack, qui empile les composants les uns sur les autres. L’utilisation d’une toile permet de placer les composants en donnant leurs coordonn´ees (x, y) `a l’int´erieur de la toile. C’est particuli`erement utile pour manipuler des composants de nature g´eom´etrique, tels que polygones, ovales, ou images num´eris´ees (bitmaps). Les coordonn´ees des composants dans la toile peuvent ˆetre chang´ees `a tout instant, ce qui permet de les d´eplacer `a l’´ecran. # let d´ecoupe_image img nx ny = let l = imagephoto__width img and h = imagephoto__height img in let tx = l / nx and ty = h / ny in let pi`eces = ref [] in for x = 0 to nx - 1 do for y = 0 to ny - 1 do let pi`ece = imagephoto__create [Width (Pixels tx); Height (Pixels ty)] in imagephoto__copy pi`ece img [ImgFrom(x * tx, y * ty, (x+1)*tx, (y+1)*ty)]; pi`eces := pi`ece :: !pi`eces done done; (tx, ty, tl !pi`eces);; Nous commen¸cons par une fonction qui charge une image au format GIF depuis un fichier et la d´ecoupe en pi`eces rectangulaires. Les param`etres nx et ny donnent le nombre de pi`eces horizontalement et verticalement. `A l’aide des fonctions de manipulations d’image fournies par le module imagephoto de CamlTk, la fonction d´ecoupe_image charge l’image depuis le fichier et la d´ecoupe en nx * ny petites images rectangulaires. La premi`ere de ces images est alors abandonn´ee pour laisser de la place au « trou » du jeu de taquin ; les autres sont renvoy´ees en r´esultat, et vont constituer les pi`eces du taquin. La fonction remplir_taquin ci-dessous se charge de positionner les pi`eces du taquin dans une toile rectangulaire c pass´ee en argument. Elle associe ensuite `a l’´ev´enement « clic souris » dans les pi`eces une fonction Caml qui permet de d´eplacer les pi`eces. Nous cr´eons un composant trou de type rectangle pour repr´esenter l’emplacement restant libre sur le taquin. Ses coordonn´ees sont conserv´ees dans les r´ef´erences trou_x et trou_y. La matrice (tableau bidimensionnel) taquin associe `a chaque coordonn´ee (x, y) 200 Interfaces graphiques le composant repr´esentant la pi`ece qui se trouve `a cet endroit. Nous la remplissons avec la liste de pi`eces pass´ee en argument. Comme il manque exactement une pi`ece pour remplir tout le jeu, la derni`ere case de la matrice reste ´egale `a trou. Chaque image de la liste pi`eces est transform´ee en ´el´ement de toile et plac´ee `a sa position initiale par la fonction canvas__create_image. Enfin, l’option Tags [Tag "pi`ece"] associe `a chaque image le nom symbolique pi`ece ; nous l’utiliserons plus tard pour associer une action `a l’´ev´enement « clic souris » dans tous les pi`eces d’un coup. # let remplir_taquin c nx ny tx ty pi`eces = let trou_x = ref (nx - 1) and trou_y = ref (ny - 1) in let trou = canvas__create_rectangle c (Pixels (!trou_x * tx)) (Pixels (!trou_y * ty)) (Pixels tx) (Pixels ty) [] in let taquin = make_matrix nx ny trou in let p = ref pi`eces in for x = 0 to nx - 1 do for y = 0 to ny - 1 do match !p with | [] -> () | pi`ece :: reste -> taquin.(x).(y) <- canvas__create_image c (Pixels (x * tx)) (Pixels (y * ty)) [ImagePhoto pi`ece; Anchor NW; Tags [Tag "pi`ece"]]; p := reste done done; let d´eplacer x y = let pi`ece = taquin.(x).(y) in canvas__coords_set c pi`ece [Pixels (!trou_x * tx); Pixels(!trou_y * ty)]; canvas__coords_set c trou [Pixels (x * tx); Pixels(y * ty); Pixels tx; Pixels ty]; taquin.(!trou_x).(!trou_y) <- pi`ece; taquin.(x).(y) <- trou; trou_x := x; trou_y := y in let jouer ei = let x = ei.ev_MouseX / tx and y = ei.ev_MouseY / ty in if x = !trou_x && (y = !trou_y - 1 || y = !trou_y + 1) || y = !trou_y && (x = !trou_x - 1 || x = !trou_x + 1) then d´eplacer x y in canvas__bind c (Tag "pi`ece") [[], ButtonPress] (BindSet ([Ev_MouseX; Ev_MouseY], jouer));; La fonction d´eplacer ci-dessus prend la pi`ece en position (x, y) et la fait glisser `a la place du trou. Elle suppose que la pi`ece (x, y) est adjacente au trou. Elle se contente d’´echanger les coordonn´ees de la pi`ece et celles du trou, tout en effectuant la mˆeme permutation dans la matrice taquin. L’appel `a canvas__bind assure que la fonction jouer est appel´ee `a chaque fois Pour aller plus loin 201 que l’utilisateur clique sur un des ´el´ements de la toile qui porte le nom symbolique pi`ece. c’est-`a-dire sur l’une des images composant les pi`eces du taquin. La fonction jouer d´etermine les coordonn´ees du clic souris `a partir de l’enregistrement ei fourni par CamlTk, v´erifie que le clic porte bien sur une pi`ece adjacente au trou, et finalement d´eplace cette pi`ece. # let rec permutation = function | [] -> [] | l -> let n = random__int (list_length l) in let (´el´ement, reste) = partage l n in ´el´ement :: permutation reste and partage l n = match l with | [] -> failwith "partage" | t^ete :: reste -> if n = 0 then (t^ete, reste) else let (´el´ement, reste’) = partage reste (n - 1) in (´el´ement, t^ete :: reste’);; Pour rendre le jeu int´eressant, il faut m´elanger initialement les pi`eces. La fonction permutation ci-dessus effectue une permutation al´eatoire d’une liste. Elle choisit au hasard un ´el´ement de la liste, puis permute r´ecursivement les autres ´el´ements de la liste, et remet l’´el´ement choisi en tˆete du r´esultat. # let taquin nom_fichier nx ny = let fen^etre_principale = openTk () in let img = imagephoto__create [File nom_fichier] in let c = canvas__create fen^etre_principale [Width(Pixels(imagephoto__width img)); Height(Pixels(imagephoto__height img))] in let (tx, ty, pi`eces) = d´ecoupe_image img nx ny in remplir_taquin c nx ny tx ty (permutation pi`eces); pack [c] []; mainLoop ();; 11.5 Pour aller plus loin CamlTk est une biblioth`eque d’une grande richesse, et la pr´esentation que nous en avons faite dans ce chapitre est forc´ement incompl`ete. Nous esp´erons cependant qu’elle aura convaincu le lecteur de la facilit´e avec laquelle on peut doter une application Caml d’une interface homme-machine de bonne facture. Pour une pr´esentation plus compl`ete de la boˆıte `a outils Tk, on se reportera `a l’ouvrage de John Ousterhout, Tcl and the Tk toolkit, Addison-Wesley, ou `a celui de Matt Welch, Practical programming in Tcl and Tk, Prentice-Hall. II Exemples complets Avertissement Enfin de vrais programmes ! — Pourquoi vrais ? Les programmes pr´ec´edents ´etaient-ils faux ? ans la premi`ere partie de ce livre, nous avons abord´e tous les m´ecanismes essentiels du langage Caml. Dans cette deuxi`eme partie, nous d´eveloppons des exemples de programmes complets, dans le but de montrer comment appliquer toute la puissance du langage `a la r´esolution de probl`emes de programmation. Nos programmes sont de « vrais » programmes : ils sont issus de probl`emes ayant une r´eelle importance pratique et se pr´esentent sous la forme d’applications ind´ependantes, utilisables en dehors du syst`eme interactif. Chaque chapitre commence par une description intuitive de ce que le programme d’exemple doit faire. Nous introduisons ensuite les notions n´ecessaires pour rendre cette sp´ecification pr´ecise, ainsi que les principaux algorithmes r´esolvant le probl`eme. Nous impl´ementons ensuite la solution en Caml, sous la forme d’un programme ind´ependant d´ecoup´e en modules. Les chapitres se terminent par un paragraphe «Pour aller plus loin », contenant en particulier des r´ef´erences bibliographiques `a l’intention du lecteur qui d´esire en savoir plus. Les exemples proviennent de divers domaines de l’informatique et supposent que le lecteur est d´ej`a vaguement familier avec les domaines concern´es. Notre but n’est pas de fournir une introduction compl`ete, partant de z´ero, `a ces domaines (plusieurs livres n’y suffiraient pas), mais de montrer le langage Caml `a l’oeuvre. Les notions suppos´ees connues font cependant partie de la culture informatique de base. Par exemple, le chapitre consacr´e `a la simulation d’un microprocesseur suppose quelques notions ´el´ementaires d’architecture des machines et une exp´erience (mˆeme minime) de la programmation en assembleur. De mˆeme, le chapitre consacr´e `a la compilation d’un langage imp´eratif simplifi´e suppose quelques connaissances en Pascal. `A chaque fois, nous fournirons des explications sur le probl`eme pr´ecis qui nous int´eresse et sur les techniques exactes que nous appliquerons ; mais nous supposerons connue la probl´ematique d’ensemble. Nous utiliserons la pr´esentation suivante pour les programmes Caml : les interfaces et les impl´ementations des modules sont entremˆel´ees avec nos commentaires ; chaque morceau de code Caml est surtitr´e par le nom du fichier auquel il appartient. Par exemple, voici un morceau du fichier toto.ml : Fichier toto.ml let x = 1 + 2;; 206 Avertissement On reconstitue le programme tout entier en recollant tous les morceaux de fichiers dans leur ordre d’apparition. `A la fin de chaque chapitre, on explique comment compiler et ex´ecuter le programme complet. Comme pour la premi`ere partie, tous les exemples pr´esent´es ont ´et´e automatiquement extraits du texte, puis compil´es et ex´ecut´es. Enfin, ces exemples sont disponibles sur le Web `a l’adresse suivante : http://caml.inria.fr/Examples/. 12 D´emonstration de propositions Mais ou et donc or ni car non si alors . . . ´ecaniser en partie le raisonnement math´ematique et transformer ainsi les ordinateurs en outils d’aide `a la d´emonstration de th´eor`emes est l’un des plus vieux projets de l’informatique. Dans ce chapitre, nous programmons un d´emonstrateur pour une classe restreinte de th´eor`emes, les formules propositionnelles du premier ordre. Ce chapitre constitue ´egalement une initiation `a la logique math´ematique ´el´ementaire. De surcroˆıt, nous compl´eterons nos connaissances sur l’analyse syntaxique et lexicale, en introduisant un g´en´erateur d’analyseurs lexicaux et les concepts de mot-cl´e r´eserv´e et de priorit´e des op´erateurs. Pour expliquer le g´en´erateur d’analyseurs lexicaux, nous serons aussi oblig´es d’introduire les tables de hachage, une structure de donn´ees tr`es utile. 12.1 La logique math´ematique La logique math´ematique traite de la v´eracit´e des phrases math´ematiques et de la validit´e des raisonnements. Elle permet de r´epondre `a des questions comme : sachant que la phrase P est vraie et que la phrase Q est fausse, est-ce que la phrase obtenue en disant « P et Q» est une phrase vraie ? Un exemple plus complexe : le raisonnement suivant est-il correct ? Puisque P est vraie et que Q est fausse, P n’est donc pas une condition n´ecessaire pour que Q soit vraie. (R´eponse `a la section 12.7.) La logique math´ematique permet de r´epondre `a ces questions en d´efinissant pr´ecis´ement les op´erations autoris´ees sur les phrases math´ematiques et la signification de ces op´erations. Une op´eration licite est par exemple de relier deux phrases math´ematiques par « et », comme dans « 2 est pair et 2 est un nombre premier ». La logique math´ematique fournit donc un sens pr´ecis `a tous les petits mots qu’on emploie dans les raisonnements, comme « donc », « or », « car », « et », « ou », etc. La logique d´efinit aussi un calcul sur les phrases math´ematiques, ind´ependant de leur signification r´eelle, qui ne s’attache qu’`a la v´erit´e des phrases. On d´eduit ainsi la v´erit´e d’une phrase complexe par un simple calcul, `a partir de la v´erit´e de ses composantes. 208 D´emonstration de propositions Par exemple, sachant que P est vraie et que Q est fausse, on saura calculer si le contraire de la phrase « P ou Q» est vrai ou faux. Les propositions La premi`ere ´etape est donc de d´efinir ce qu’on entend par phrase math´ematique. En effet, tout ´enonc´e n’est pas forc´ement une phrase math´ematique. Le crit`ere minimal est la non-contradiction : une phrase math´ematique peut ˆetre vraie ou fausse, mais on exige qu’elle ne soit pas `a la fois vraie et fausse. Un exemple paradigmatique de phrase contradictoire est le paradoxe du menteur : c’est la simple phrase « Je mens ». En effet, cette phrase est `a la fois vraie et fausse. On le d´emontre facilement en la supposant d’abord vraie et en montrant qu’elle est alors forc´ement fausse, puis en la supposant fausse et en montrant qu’alors elle est vraie. 1. Supposons la phrase vraie. C’est donc qu’il est vrai que la personne qui parle ment, qu’elle ne dit pas la v´erit´e. Donc cette personne ´enonce des phrases fausses et donc la phrase qu’elle vient d’´enoncer, « Je mens », est fausse. 2. Supposons la phrase fausse. La phrase « Je mens » est donc inexacte. C’est donc que le locuteur ne ment pas et dit la v´erit´e. C’est donc que la phrase qu’il ´enonce est vraie. Dans les deux cas, la phrase est `a la fois vraie et fausse : ce n’est donc pas une phrase math´ematique. Une phrase acceptable est appel´ee proposition. Une proposition peut donc ˆetre soit vraie soit fausse. Les valeurs « vrai » et « faux » sont appel´ees valeurs de v´erit´e ; nous les noterons en abr´eg´e v et f. Pour ceux qui penseraient — `a tort — que le paradoxe du menteur vient sans doute de confusions dues `a l’impr´ecision du langage courant, on peut prendre d’autres exemples, formul´es dans un langage tout `a fait math´ematique cette fois-ci. Par exemple : « l’ensemble de tous les ensembles qui ne sont pas ´el´ements d’eux-mˆemes est ´el´ement de lui-mˆeme ». Par le mˆeme raisonnement que pour le paradoxe du menteur, on voit que cet ´enonc´e est `a la fois vrai et faux. Notez le lien avec les d´efinitions r´ecursives qui ne sont pas bien fond´ees : nous avons d´ej`a soulign´e qu’une phrase qui pr´esente des autor´ef´erences est susceptible de mener au non-sens, si l’on n’y prend pas garde (section 2.1). Les connecteurs propositionnels Les op´erations que nous allons d´efinir sur les propositions sont appel´ees connecteurs propositionnels, puisqu’elles relient des propositions pour fabriquer d’autres propositions. Nous commen¸cons par le « contraire » d’une proposition, qu’on appelle aussi sa n´egation. C’est le connecteur « non » : si P est une proposition, alors non P est une proposition, qui est fausse si P est vraie et qui est vraie si P est fausse. Techniquement, la proposition non P est souvent not´ee ¬P ou encore P. Pour ne pas multiplier les notations, nous n’utiliserons pas les noms techniques des connecteurs, mais leur nom vulgaire. Ainsi, nous continuerons `a noter non le connecteur ¬. On d´efinit formellement le connecteur non en envisageant toutes les valeurs de v´erit´e possibles de son argument La logique math´ematique 209 et en donnant pour chacune d’elles la valeur de v´erit´e correspondante de non P. On ´ecrit donc cette d´efinition sous la forme d’un tableau de cas, qu’on nomme « table de v´erit´e » : P non P v f f v Cette table comprend sur chaque colonne une proposition et ses diff´erentes valeurs de v´erit´e possibles. La premi`ere ligne dresse ainsi la liste de toutes les propositions d´ecrites par la table. Les autres lignes donnent les valeurs de v´erit´e de toutes les propositions de fa¸con coh´erente, c’est-`a-dire selon les valeurs prises par les propositions de base. La table pr´ec´edente comprend donc deux lignes, puisqu’il y a deux cas possibles pour P. La deuxi`eme ligne indique donc que lorsque P vaut v, non P vaut f, et la troisi`eme ligne que non P vaut v quand P vaut f. Nous d´efinissons maintenant des op´erations binaires, le « et » (la conjonction) et le « ou » (la disjonction). Si P est une proposition et Q est une proposition, alors P et Q est une proposition. Par d´efinition, P et Q n’est vraie que si P et Q sont simultan´ement vraies. La proposition P et Q est not´ee P ∧ Q en math´ematiques. L’op´eration et est aussi d´efinie par une table de v´erit´e, mais le tableau comporte plus de lignes que pour l’op´eration non, car il faut envisager tous les cas possibles pour P et pour Q, c’est-`a-dire quatre cas. P Q P et Q v v v v f f f v f f f f Remarquez que P et Q est fausse d`es que l’une des propositions P ou Q est fausse. Le « ou » est sym´etrique du « et », en rempla¸cant vrai par faux : par d´efinition, P ou Q n’est fausse que si P et Q sont simultan´ement fausses. La proposition P ou Q est not´ee P ∨ Q en math´ematiques. P Q P ou Q v v v v f v f v v f f f Remarquez que P ou Q est vraie d`es que l’une des propositions P ou Q est vraie. Ce sont les seules d´efinitions n´ecessaires en logique ´el´ementaire. Toutes les autres constructions du raisonnement s’expriment en fonction de celles-ci. Cette ´economie de moyens est l’une des beaut´es de la logique. Toute la th´eorie est construite sur les notions ´el´ementaires et intuitives de valeur de v´erit´e, de « et », de « ou » et de « non ». Remarques sur les connecteurs « et » et « ou » Il faut noter que le « ou » de la logique est inclusif, c’est-`a-dire que P ou Q est encore vraie si P et Q sont toutes les deux vraies. Nous venons de le voir, P ou Q est vraie 210 D´emonstration de propositions d`es que l’une des propositions est vraie ; si les deux propositions P et Q sont vraies, P ou Q est a fortiori vraie, on serait tent´e de dire « encore plus vraie », puisqu’il y a deux raisons pour qu’elle soit vraie. La difficult´e vient de ce que l’emploi de « ou » dans la langue commune n’est pas toujours celui-l`a. En r´ealit´e la s´emantique du « ou » dans la langue parl´ee est assez floue. Consid´erez les phrases suivantes : Fromage ou dessert. D´efense de fumer ou de cracher. Mange ta soupe ou tu auras une fess´ee. Dans « fromage ou dessert » le ou est exclusif : on aura du fromage, ou bien du dessert, mais pas les deux. En revanche, le « ou » de « fumer ou cracher » a le sens des math´ematiques : il est inclusif. En effet, il est interdit de fumer, il est interdit aussi de cracher, mais il est « encore plus » interdit de cracher tout en fumant. Finalement, le « ou » de «mange ta soupe ou tu auras une fess´ee » a le sens d’une d´eduction ; on pourrait le remplacer par sinon : «mange ta soupe sinon tu auras une fess´ee ». C’est le sens aussi d’un « si alors » : « si tu ne manges pas ta soupe alors tu auras une fess´ee ». Cette signification particuli`ere du « ou » n’est pas un hasard, c’est au contraire l’exacte d´efinition math´ematique de l’implication. Le raisonnement si P alors Q est synonyme de (non P) ou Q. Par exemple « si nous ne nous hˆatons pas nous serons en retard » est synonyme de « hˆatons-nous ou nous serons en retard ». L’implication Le raisonnement « si alors » est appel´e implication en logique. L’implication est traditionnellement not´ee⇒: si P et Q sont des propositions, alors par d´efinition P ⇒ Q est une proposition, qui a la mˆeme valeur de v´erit´e que (non P) ou Q. La proposition P ⇒ Q se lit « P implique Q». Nous avons maintenant d´efini pr´ecis´ement toutes les op´erations de base sur les propositions. Il nous reste `a montrer les m´ethodes de calcul sur ces op´erations. 12.2 Calculs de tables de v´erit´e Nous commen¸cons par un calcul « `a la main », qui nous permettra de comprendre comment va marcher le programme de d´emonstration automatique. Une premi`ere d´emonstration Nous allons ´etablir la table de v´erit´e de l’implication. Plus pr´ecis´ement, nous d´emontrons que la proposition P ⇒ Q a la table de v´erit´e suivante : P Q P ⇒ Q v v v v f f f v v f f v Calculs de tables de v´erit´e 211 Nous avons d´efini P ⇒ Q comme (non P) ou Q, ce qui signifie que ce sont les mˆemes propositions, ou encore qu’elles ont toujours la mˆeme valeur de v´erit´e, quelles que soient les valeurs de v´erit´e des propositions P et Q. Le calcul de la table de v´erit´e de l’implication est donc tr`es simple : on proc`ede par ´etapes dans une table de v´erit´e o`u l’on a ´enum´er´e toutes les possibilit´es pour P et Q: on calcule d’abord la proposition non P dans tous les cas de figures, puis le « ou » de non P et de Q. On obtient finalement : P Q non P (non P) ou Q v v f v v f f f f v v v f f v v Constatez que l’implication est donc vraie si l’hypoth`ese est fausse (deux derni`eres lignes du tableau). Ceci correspond `a l’intuition : lorsqu’on a un th´eor`eme vrai P ⇒ Q, mais qu’on n’a pas l’hypoth`ese P, alors on ne peut ´evidemment rien en d´eduire sur Q, puisque dans ce cas Q peut aussi bien ˆetre vraie que fausse (toujours les deux derni`eres lignes du tableau). D’autre part, il est impossible d’attribuer une autre valeur de v´erit´e `a l’implication lorsque l’hypoth`ese n’est pas vraie. En effet, si l’on mettait f `a la place de v dans les deux derni`eres lignes de la colonne de (non P) ou Q, cela signifierait qu’un th´eor`eme P ⇒ Q devient faux d`es que son hypoth`ese est fausse, ce qui serait absurde. On r´esume parfois cette situation en disant « le faux implique n’importe quoi » ; nous pr´ef´erons la formulation « on ne peut rien d´eduire d’un th´eor`eme dont l’hypoth`ese n’est pas v´erifi´ee », ou encore « un th´eor`eme reste vrai mˆeme quand il ne s’applique pas ». L’´equivalence Vous connaissez sans doute d´ej`a un autre connecteur propositionnel dont nous n’avons pas parl´e : le « si et seulement si », qu’on appelle l’´equivalence et qu’on note d’habitude ⇔. Intuitivement, deux propositions sont ´equivalentes quand elles sont toujours vraies ou fausses en mˆeme temps. Par exemple, si P ⇔ Q est vraie, on d´eduit imm´ediatement la valeur de v´erit´e de Q si l’on connaˆıt celle de P : quand P est vraie on en d´eduit que Q est vraie et quand P est fausse on en d´eduit que Q est fausse. Nous n’avons pas d´efini ce connecteur car il s’exprime en fonction de ceux que nous connaissons d´ej`a. Vous savez peut-ˆetre que P ⇔ Q est une double implication, comme le sugg`ere la double fl`eche. En effet P ⇔ Q signifie que P implique Q (la proposition directe) et que de plus Q implique P (la proposition r´eciproque). En fait, on d´efinit l’´equivalence par cette propri´et´e : la proposition P ⇔ Q vaut, par d´efinition, ce que vaut la proposition (P ⇒ Q) et (Q ⇒ P). Comme ci-dessus, nous ´etablissons la table de v´erit´e de l’´equivalence, en proc´edant par ´etapes au calcul de la table de v´erit´e de la proposition complexe (P ⇒ Q) et (Q ⇒ P). Nous obtenons : P Q P ⇒ Q Q ⇒ P (P ⇒ Q) et (Q ⇒ P) v v v v v v f f v f f v v f f f f v v v 212 D´emonstration de propositions Cette table de v´erit´e correspond `a l’intuition : l’´equivalence de deux propositions n’est vraie que dans le cas o`u les deux propositions ont la mˆeme valeur de v´erit´e. D’un point de vue logique, des propositions ´equivalentes sont indiscernables : elles sont donc ´egales au sens logique. En effet, la logique ne distingue les propositions que par leur valeur de v´erit´e, pas par leur texte. Deux propositions ´equivalentes ne sont donc pas logiquement diff´erentes. C’est le mˆeme ph´enom`ene qu’en arithm´etique, o`u l’on ne peut distinguer 1 + 1 de 2, bien que ces deux expressions ne soient pas syntaxiquement les mˆemes. On sent bien que le calcul des tables de v´erit´e est automatisable et qu’un programme remplirait facilement les colonnes des tableaux `a notre place, en calculant ainsi la valeur de v´erit´e d’une proposition complexe en fonction des propositions qui la composent. Le programme engendrerait plus facilement que nous toutes les combinaisons possibles pour les diff´erentes propositions ´el´ementaires qui interviennent. En effet cette combinatoire augmente tr`es vite : pour 2 propositions P et Q nous avons 4 lignes dans le tableau, mais pour 3 il en faudrait 8, pour 4 propositions 16 lignes, et ainsi de suite (pour n propositions 2n lignes). Notre d´emonstrateur de th´eor`emes fonctionne exactement selon ce principe. 12.3 Le principe des d´emonstrations Notre d´emonstrateur est restreint aux th´eor`emes de logique ´el´ementaire. Or, un th´eor`eme n’est rien d’autre qu’une proposition qui est toujours vraie. Ceci conduit d’ailleurs `a une petite difficult´e : en math´ematiques, on n’´ecrit jamais qu’une proposition est vraie. On se contente de l’´ecrire simplement, ce qui sous-entend qu’elle est vraie. On ´ecrira par exemple : Si un triangle a trois angles de 60 degr´es alors ses trois cˆot´es ont mˆeme longueur. et non pas : L’implication « si un triangle . . . alors . . . » est vraie. En revanche, on pr´ecise explicitement qu’une proposition est fausse. Le but du d´emonstrateur est donc de montrer qu’une proposition est toujours vraie. Pour cela, il d´emontre que pour toutes les valeurs possibles des propositions ´el´ementaires, la proposition `a d´emontrer a toujours la valeur de v´erit´e « vrai ». Conceptuellement, cela correspond `a ´etablir la table de v´erit´e de la proposition et `a v´erifier que la colonne de droite, celle de la proposition `a d´emontrer, ne contient que des valeurs v. En logique, une proposition toujours vraie est aussi appel´ee une tautologie. Nous dirons donc que notre programme est un d´emonstrateur de tautologies. Nous lui ajoutons un raffinement suppl´ementaire : au cas o`u la proposition qui lui est soumise n’est pas une tautologie, le programme renvoie une r´efutation, c’est-`a-dire un choix de valeurs de v´erit´e pour les propositions ´el´ementaires qui rend fausse la proposition soumise. L’architecture du programme suit exactement la m´ethode des tables de v´erit´e : pour calculer les valeurs de v´erit´e des lignes de la table nous allons ´ecrire un sous-programme qui calcule la valeur de v´erit´e d’une proposition en fonction de ses composantes (les Repr´esentation et v´erification des propositions 213 « et », « ou », « non » qui interviennent) ; ensuite, pour produire l’ensemble des lignes de la table, nous ´ecrirons une autre fonction qui envisagera l’ensemble des valeurs possibles des propositions ´el´ementaires. Pour simplifier l’utilisation du d´emonstrateur, nous ferons finalement un analyseur syntaxique, qui nous permettra d’entrer facilement les propositions. Cela devient presque de la routine maintenant : nous d´efinirons le type des propositions et l’analyseur syntaxique analysera une chaˆıne de caract`eres, qu’il transformera en une valeur du type des propositions. 12.4 Repr´esentation et v´erification des propositions Nous commen¸cons par le module prop, qui d´efinit le type des propositions et impl´emente les fonctions d’´evaluation d’une proposition et de g´en´eration de la combinatoire des possibilit´es d´ecrites ci-dessus. Le type des propositions Le type des propositions comporte des constructeurs pour les connecteurs de base Non, Et, Ou, et des constructeurs pour les connecteurs d´efinis `a partir des connecteurs de base, Implique et ´Equivalent. Pour repr´esenter les propositions ´el´ementaires comme P ou Q, qu’on appelle aussi variables propositionnelles, on fournit le constructeur Variable qui prend en argument une chaˆıne de caract`eres (le nom de la proposition). Ces variables repr´esentent les propositions dont on ne connaˆıt pas la valeur et dont les diff´erentes valeurs de v´erit´e forment les lignes des tables de v´erit´e. Une variable est donc une proposition ind´efinie, repr´esent´ee par un simple nom. Une variable qui intervient dans une proposition R est dite variable libre de R. Par exemple, P est une variable libre de la proposition P ou Q. Plus pr´ecis´ement, la proposition P ou Q comporte deux variables libres, P et Q. Pour des raisons de commodit´e, on a ajout´e deux constructeurs constants particuliers Vrai et Faux, qui repr´esentent deux propositions particuli`eres, la proposition toujours vraie et la proposition toujours fausse. Ces propositions correspondent aux valeurs de v´erit´e, consid´er´ees comme des propositions. Fichier prop.mli type proposition = | Vrai | Faux | Non of proposition | Et of proposition * proposition | Ou of proposition * proposition | Implique of proposition * proposition | ´Equivalent of proposition * proposition | Variable of string;; exception R´efutation of (string * bool) list;; value v´erifie_tautologie: proposition -> string list -> unit and variables_libres: proposition -> string list;; 214 D´emonstration de propositions Le module prop exporte deux fonctions : v´erifie_tautologie, qui v´erifie qu’une proposition est une tautologie ou sinon d´eclenche l’exception R´efutation, et variables_libres, qui calcule la liste des variables libres d’une proposition. L’´evaluateur de propositions Une proposition comporte donc parfois des variables libres. Mais pour calculer la valeur de v´erit´e d’une proposition, il faut absolument connaˆıtre la valeur de ses variables libres. Comme d’habitude, notre machine ne peut calculer qu’en connaissant la valeur de toutes les entit´es qui interviennent dans l’expression `a calculer (cf. l’exemple « x − x » de la section 1.3). Nous utiliserons donc des liaisons qui associent une valeur bool´eenne `a toute variable libre de la proposition ´etudi´ee. ´Etant donn´e un ensemble de liaisons de variables, l’´evaluation d’une proposition s’op`ere en traduisant simplement les connecteurs de la logique `a l’aide des op´erateurs logiques de Caml : not, &&, ||, =. Fichier prop.ml let rec ´evalue_dans liaisons = function | Vrai -> true | Faux -> false | Non p -> not (´evalue_dans liaisons p) | Et (p, q) -> (´evalue_dans liaisons p) && (´evalue_dans liaisons q) | Ou (p, q) -> (´evalue_dans liaisons p) || (´evalue_dans liaisons q) | Implique (p, q) -> (not (´evalue_dans liaisons p)) || (´evalue_dans liaisons q) | ´Equivalent (p, q) -> ´evalue_dans liaisons p = ´evalue_dans liaisons q | Variable v -> assoc v liaisons;; La fonction ´evalue_dans oblige donc `a faire la relation entre les connecteurs de la logique et les op´erateurs logiques de Caml. Il faudrait d´emontrer, en analysant tous les cas possibles des propositions arguments des connecteurs, que le « non » de la logique correspond bien au not de Caml, de mˆeme que « et » correspond `a && et « ou » `a ||. Nous supposerons cette d´emonstration faite, dans la mesure o`u cette traduction est tr`es naturelle et intuitive. L’implication est ´evalu´ee en utilisant sa d´efinition : P ⇒ Q se calcule comme (non P) ou Q. L’´equivalence est ´evalu´ee en v´erifiant simplement que ses deux arguments ont la mˆeme valeur de v´erit´e. Le v´erificateur de propositions Le principe du v´erificateur est d’´evaluer la proposition pour toutes les valeurs possibles de ses variables libres : si l’une des ´evaluations renvoie faux, alors la proposition n’est pas une tautologie. On signale ce fait en d´eclenchant l’exception R´efutation avec pour argument une liste de paires (chaˆıne, bool´een) d´ecrivant l’ensemble des valeurs attribu´ees aux variables qui ont permis de calculer la valeur de v´erit´e f pour la proposition. Cette liste constitue bien une r´efutation de la proposition, puisqu’elle montre par un exemple que la proposition n’est pas universellement vraie. Repr´esentation et v´erification des propositions 215 Fichier prop.ml let rec v´erifie_lignes proposition liaisons variables = match variables with | [] -> if not ´evalue_dans liaisons proposition then raise (R´efutation liaisons) | var :: autres -> v´erifie_lignes proposition ((var, true) :: liaisons) autres; v´erifie_lignes proposition ((var, false):: liaisons) autres;; let v´erifie_tautologie proposition variables = v´erifie_lignes proposition [] variables;; La fonction v´erifie_lignes v´erifie toutes les lignes de la table de v´erit´e, sans la construire effectivement. Elle prend en argument une proposition, un ensemble de liaisons et la liste des variables libres de la proposition. Elle lie alors les variables libres `a des valeurs true ou false, puis ´evalue la proposition. En effet, la r`egle [] -> proc`ede `a l’´evaluation de la proposition, lorsqu’il n’y a plus de variables `a lier. La seconde r`egle correspond au cas o`u il y a des variables `a lier ; elle ex´ecute une s´equence de deux appels r´ecursifs `a v´erifie_lignes, en liant la premi`ere variable rencontr´ee d’abord `a true, puis `a false. Ce programme assure donc que toutes les combinaisons possibles seront envisag´ees et si la v´erification ne d´eclenche jamais l’exception R´efutation on aura effectivement prouv´e que la proposition s’´evalue toujours en true dans toutes les liaisons possibles de ses variables. La fonction v´erifie_tautologie se contente d’appeler v´erifie_lignes avec un ensemble de liaisons initialement vide. Dans un style apparemment plus « fonctionnel », on ´ecrirait : let rec v´erifie_lignes proposition liaisons = function | [] -> ´evalue_dans liaisons proposition || raise (R´efutation liaisons) | var :: autres -> v´erifie_lignes proposition ((var, true) :: liaisons) autres && v´erifie_lignes proposition ((var, false):: liaisons) autres;; Cette version n’est pas plus claire que la pr´ec´edente : elle est trompeuse car bien qu’elle semble calculer un bool´een, son r´esultat n’est pas int´eressant. En effet, elle retourne toujours le bool´een true si la proposition est une tautologie, ou l`eve une exception si la proposition est r´efutable. C’est donc bien une proc´edure, puisqu’elle fonctionne par effets : l’effet attendu est soit « ´evaluation r´eussie », soit un d´eclenchement d’exception. Il ne sert `a rien de la d´eguiser en fonction . . . Si l’on renonce `a renvoyer une r´efutation de la proposition analys´ee, il est possible d’´ecrire une vraie fonction qui calcule vraiment un bool´een. Malheureusement on perd la liaison des variables qui a prouv´e que la proposition n’est pas une tautologie et il faut alors ´ecrire une autre fonction, compl`etement analogue, pour renvoyer une r´efutation. Cet exemple nous montre un autre int´erˆet des exceptions : dans certains cas une fonction peut calculer en fait deux r´esultats de type diff´erent, l’un v´ehicul´e par le m´ecanisme normal des appels de fonctions, l’autre transport´e par une exception (v´erifie_lignes calcule un bool´een dans le cas d’une tautologie et une liste d’association (nom de variable, valeur bool´eenne) dans le cas contraire). 216 D´emonstration de propositions Une remarque de complexit´e : comme nous l’avons d´ej`a vu, le nombre de lignes d’une table de v´erit´e est 2n, o`u n est le nombre de variables qui interviennent dans la table. Notre fonction v´erifie_tautologie suit exactement la m´ethode des tables de v´erit´e ; elle a donc une complexit´e exponentielle. Ce n’est pas tr`es grave pour nous, car nous nous limiterons `a trois variables au plus. C’est un probl`eme actuellement ouvert que de savoir s’il existe des algorithmes d’une meilleure complexit´e que le nˆotre pour calculer la table de v´erit´e d’une formule. Dans le pire des cas on ne sait ´evidemment pas faire mieux, mais dans certains cas particuliers, on parvient `a calculer les tables de v´erit´e de formules o`u interviennent des centaines, voire des milliers de variables, ce qui est absolument hors de port´ee de notre programme. On utilise pour cela des structures sophistiqu´ees de partage de tables de v´erit´e, comme par exemple les binary decision diagrams. Calcul des variables libres Pour appeler la fonction v´erifie_tautologie, nous devons disposer d’une fonction qui d´etermine l’ensemble des propositions ´el´ementaires d’une proposition, ce que nous appelons aussi ses variables libres. La liste des variables libres s’obtient facilement par un parcours r´ecursif de la proposition, `a la recherche de sous-propositions de la forme Variable v. Fichier prop.ml let rec variables accu proposition = match proposition with | Variable v -> if mem v accu then accu else v :: accu | Non p -> variables accu p | Et (p, q) -> variables (variables accu p) q | Ou (p, q) -> variables (variables accu p) q | Implique (p, q) -> variables (variables accu p) q | ´Equivalent (p, q) -> variables (variables accu p) q | _ -> accu;; let variables_libres proposition = variables [] proposition;; La seule difficult´e est que les variables ne doivent pas ˆetre r´ep´et´ees dans la liste r´esultat. Par exemple, si on cherche les variables de la proposition P et P, on va rencontrer deux fois le terme (Variable "P"). Pourtant, la chaˆıne "P" ne doit apparaˆıtre qu’une fois dans le r´esultat de variables_libres. C’est pourquoi, lorsqu’on rencontre Variable v, on teste si v n’appartient pas d´ej`a `a la liste des variables collect´ees jusqu’`a pr´esent (`a l’aide de la fonction pr´ed´efinie mem, qui se comporte comme la fonction membre de la section 7.3). La fonction variables maintient donc un accumulateur des variables d´ej`a collect´ees : c’est l’argument accu, qu’on g`ere soigneusement lors des appels r´ecursifs. Par exemple, dans le cas d’un op´erateur binaire, comme et, on appelle r´ecursivement variables sur le deuxi`eme argument de l’op´erateur, mais avec un accumulateur obtenu en collectant les variables du premier argument : Et (p, q) -> variables (variables accu p) q La fonction principale variables_libres se r´eduit `a l’appel de variables avec un accumulateur initialement vide. Syntaxe concr`ete des propositions 217 12.5 Syntaxe concr`ete des propositions Nous d´efinissons maintenant la syntaxe concr`ete des propositions, et les fonctions qui transforment la syntaxe concr`ete en syntaxe abstraite. Reprenant l’approche du chapitre 9, nous allons proc´eder en deux temps : analyse lexicale, pour obtenir une suite de lex`emes `a partir d’une suite de caract`eres, puis analyse syntaxique, pour construire un arbre de syntaxe abstraite `a partir d’une suite de lex`emes. L’analyseur lexical L’analyseur lexical dont nous avons besoin est tr`es proche de celui du chapitre 9 : il doit savoir supprimer les blancs, reconnaˆıtre les identificateurs (noms de variables) et distinguer les symboles sp´eciaux comme les parenth`eses. Cependant, nous ne pouvons pas r´eutiliser tel quel l’analyseur du chapitre 9. D’une part, nous avons besoin de reconnaˆıtre des lex`emes form´es d’une suite de symboles, comme par exemple => ou <=>. D’autre part, nous voulons introduire la notion de mot-cl´e r´eserv´e. Rappelons qu’un mot-cl´e est une suite de caract`eres qui a la forme d’un identificateur, mais qui joue un rˆole sp´ecial dans le langage, par exemple comme op´erateur infixe ou pour introduire des constructions du langage. Ainsi, if, then, else sont des mots-cl´es du langage Caml. De mˆeme, vrai, faux, et, ou, non sont des mots-cl´es de la syntaxe concr`ete des propositions. Un mot-cl´e est dit r´eserv´e s’il ne peut pas ˆetre employ´e comme identificateur. Par exemple, en Caml, il est impossible d’employer if comme un nom de variable : une phrase comme let if = 3 est syntaxiquement incorrecte. Le mot-cl´e if est donc r´eserv´e en Caml (ainsi que tous les autres mots-cl´es). En revanche, le mini-Logo du chapitre 9 n’a pas de mots-cl´es r´eserv´es : on ´ecrit sans probl`eme la proc´edure pour carr´e :r´ep`ete r´ep`ete 4 [av :r´ep`ete td 90]. en utilisant ainsi l’identificateur r´ep`ete `a la fois comme mot-cl´e et comme nom de variable. Dans le cas de mini-Logo ce n’est pas catastrophique, puisque les variables sont explicitement diff´erenci´ees par le symbole « : » qui les pr´ec`ede. Mais un langage comme PL/1 n’a pas cette convention et pourtant ne r´eserve aucun mot-cl´e, autorisant donc des phrases comme : if then = else then else = if else then = if Comme on le voit sur cet exemple, ne pas r´eserver les mots-cl´es peut conduire `a des programmes vraiment illisibles. Nous allons donc r´eserver les mots-cl´es de la syntaxe concr`ete des propositions. Les changements `a apporter `a l’analyseur lexical sont minimes : lorsqu’on a reconnu une suite de caract`eres qui a la forme d’un identificateur, il faut tester si cette suite de caract`eres est un mot-cl´e ou non et renvoyer des lex`emes diff´erents selon le cas. Par exemple, ayant lu truc, qui n’est pas un mot-cl´e, on renverra le lex`eme (Ident "truc") ; mais si on lit ou, on renverra le lex`eme (MC "ou"). Le constructeur MC est l’abr´eviation de «mot-cl´e ». Autant les conventions lexicales de base (qu’est-ce qu’un entier, qu’est-ce qu’un identificateur, . . . ) sont souvent les mˆemes d’un langage `a un autre, autant les mots-cl´es sont hautement sp´ecifiques au langage. Dans le but de rendre notre analyseur lexical r´eutilisable par la suite, pour d’autres langages que les propositions, nous n’allons 218 D´emonstration de propositions pas mettre « en dur » la liste des mots-cl´es dans le code de l’analyseur. Au contraire, l’analyseur va prendre en param`etre la liste des mots-cl´es et renvoyer une fonction d’analyse lexicale (de type char stream -> lex`eme stream) sp´ecialis´ee pour cette liste de mots-cl´es. La fonction principale qui fait correspondre une fonction d’analyse `a une liste de mots-cl´es est plus qu’un analyseur lexical, c’est toute une famille d’analyseurs lexicaux en puissance. C’est pourquoi nous l’appelons « g´en´erateur d’analyseurs lexicaux », ou « analyseur lexical universel ». Les mots « g´en´erateur » et « universel » sont un peu forts car cette fonction impose des conventions lexicales fixes ; elle n’est donc pas « universelle » et ne remplace en aucun cas un v´eritable g´en´erateur d’analyseurs comme camllex (cf. le chapitre 7 du Manuel de r´ef´erence du langage Caml ). Pourtant cet analyseur lexical « universel » suffira amplement aux besoins de ce livre : c’est le dernier analyseur lexical que nous ´ecrirons, par la suite nous le r´eutiliserons tel quel pour tous les langages dont nous aurons besoin. L’analyseur lexical « universel » se pr´esente sous la forme d’un module, lexuniv, dont voici l’interface : Fichier lexuniv.mli type lex`eme = | MC of string | Ident of string | Entier of int;; value construire_analyseur: string list -> (char stream -> lex`eme stream);; On trouvera une impl´ementation de ce module `a la fin de ce chapitre (section 12.8). L’analyseur syntaxique L’analyse syntaxique des propositions ressemble beaucoup `a l’analyse syntaxique des expressions dans le mini-Logo. La principale nouveaut´e est que nous allons prendre en compte les priorit´es entre op´erateurs. Par exemple, vous savez qu’en arithm´etique 1+2×3 signifie 1+(2×3) et que 1×2+3 veut dire (1×2)+3. Les deux ´ecritures sont tol´er´ees et ont la mˆeme signification. On dit que l’op´erateur × a une priorit´e plus ´elev´ee que l’op´erateur +. Cependant, l’analyseur syntaxique du mini-Logo traduit 1*2+3 par Produit(Constante 1, Somme(Constante 2, Constante 3)), ce qui correspond `a 1×(2+3). En effet, il analyse d’abord 1 comme une expression, puis voit l’op´erateur * et se rappelle donc r´ecursivement pour lire l’expression `a la droite du *, dont il fait le deuxi`eme argument du constructeur Produit. L’analyseur syntaxique du mini-Logo ne tient donc pas compte des priorit´es relatives des op´erateurs. Nous allons impl´ementer des conventions de priorit´e analogues `a celles de l’arithm´etique pour les op´erations de la logique : et est plus prioritaire que ou, qui est plus prioritaire que =>, etc. Dans ce but, l’analyseur syntaxique est stratifi´e en fonctions qui analysent des propositions de priorit´e de plus en plus faible. La fonction proposition0 analyse ainsi les propositions les plus simples, identificateurs, bool´eens ou expressions entre parenth`eses. La fonction proposition1 analyse les propositions qui commencent par non, ou qui sont des propositions simples. Les autres Syntaxe concr`ete des propositions 219 fonctions proposition2 `a proposition5 analysent respectivement les propositions qui comportent un et, un ou, une implication ou une ´equivalence. Fichier asynt.ml #open "prop";; #open "lexuniv";; let rec lire_proposition f = proposition5 f and proposition0 = function | [< ’Ident s >] -> Variable s | [< ’MC "vrai" >] -> Vrai | [< ’MC "faux" >] -> Faux | [< ’MC "("; lire_proposition p; ’MC ")" >] -> p and proposition1 = function | [< ’MC "non"; proposition0 p >] -> Non p | [< proposition0 p >] -> p and proposition2 = function | [< proposition1 p; (reste2 p) q >] -> q and reste2 p = function | [< ’MC "et"; proposition1 q; (reste2 (Et (p, q))) r >] -> r | [<>] -> p and proposition3 = function | [< proposition2 p; (reste3 p) q >] -> q and reste3 p = function | [< ’MC "ou"; proposition2 q; (reste3 (Ou (p, q))) r >] -> r | [<>] -> p and proposition4 = function | [< proposition3 p; (reste4 p) q >] -> q and reste4 p = function | [< ’MC "=>"; proposition3 q; (reste4 (Implique (p, q))) r >] -> r | [<>] -> p and proposition5 = function | [< proposition4 p; (reste5 p) q >] -> q and reste5 p = function | [< ’MC "<=>"; proposition4 q; (reste5 (´Equivalent(p,q))) r >] -> r | [<>] -> p;; Les fonctions proposition1 `a proposition5 sont toutes construites sur le mˆeme moule. Elles cherchent d’abord une proposition de niveau plus simple, puis appellent une fonction reste. Cette fonction se charge de d´etecter l’op´erateur, par exemple et pour reste2, suivi d’une proposition de mˆeme niveau et ´eventuellement d’autres op´erateurs du mˆeme type (d’autres et pour reste2). Remarquez que la fonction reste prend en argument l’arbre de syntaxe abstraite jusqu’`a pr´esent construit et lui ajoute les op´erateurs rencontr´es ; d’o`u l’appel reste2 p dans la fonction proposition2 et l’appel r´ecursif reste2 (Et (p,q)) dans la fonction reste2. 220 D´emonstration de propositions La similitude entre les fonctions qui g`erent les priorit´es des op´erateurs sugg`ere d’´ecrire une fonction g´en´erique qui prenne en argument l’op´erateur binaire `a reconnaˆıtre et la fonction de reconnaissance de ses arguments, puis construise automatiquement la fonction reste correspondante. C’est possible avec la fonctionnelle lire_op´eration ci-dessous. Fichier asynt.ml let lire_op´eration lire_op´erateur lire_base constructeur = let rec lire_reste e1 = function | [< lire_op´erateur _; lire_base e2; (lire_reste (constructeur (e1, e2))) e >] -> e | [< >] -> e1 in function [< lire_base e1; (lire_reste e1) e >] -> e;; L’argument lire_op´erateur est une fonction qui reconnaˆıt les flux commen¸cant par l’op´erateur qui nous int´eresse, par exemple function [< ’MC "+" >] -> () dans le cas de l’op´erateur +. L’argument lire_base est une fonction suppos´ee reconna ˆıtre les arguments de l’op´erateur. Enfin, l’argument constructeur est une fonction qui re¸coit les arbres de syntaxe abstraite des arguments et doit construire l’arbre de syntaxe abstraite de l’expression tout enti`ere. En utilisant cette puissante fonctionnelle lire_op´eration, l’analyseur syntaxique se simplifie notablement. Fichier asynt.ml let rec lire_proposition f = proposition5 f and proposition0 = function | [< ’Ident s >] -> Variable s | [< ’MC "vrai" >] -> Vrai | [< ’MC "faux" >] -> Faux | [< ’MC "("; lire_proposition p; ’MC ")" >] -> p and proposition1 = function | [< ’MC "non"; proposition0 p >] -> Non p | [< proposition0 p >] -> p and proposition2 flux = lire_op´eration (function [< ’MC "et" >] -> ()) proposition1 (function (p,q) -> Et (p,q)) flux and proposition3 flux = lire_op´eration (function [< ’MC "ou" >] -> ()) proposition2 (function (p,q) -> Ou (p,q)) flux and proposition4 flux = lire_op´eration (function [< ’MC "=>" >] -> ()) proposition3 Le v´erificateur de tautologies 221 (function (p,q) -> Implique (p,q)) flux and proposition5 flux = lire_op´eration (function [< ’MC "<=>" >] -> ()) proposition4 (function (p,q) -> ´Equivalent (p,q)) flux;; Pour construire la fonction principale d’analyse syntaxique, nous engendrons un analyseur lexical en appliquant (partiellement) la fonction construire_analyseur `a la liste des mots-cl´es, puis nous composons cet analyseur lexical avec la fonction lire_proposition. Fichier asynt.ml let analyseur_lexical = construire_analyseur ["vrai"; "faux"; "("; ")"; "non"; "et"; "ou"; "=>"; "<=>"];; let analyse_proposition cha^ıne = lire_proposition (analyseur_lexical (stream_of_string cha^ıne));; Pour finir, nous cachons toutes les fonctions interm´ediaires d’analyse syntaxique, en exportant uniquement la fonction principale. Fichier asynt.mli #open "prop";; value analyse_proposition: string -> proposition;; 12.6 Le v´erificateur de tautologies Tout est prˆet pour r´ealiser un d´emonstrateur de th´eor`emes se pr´esentant sous la forme d’une boucle d’interaction qui lit des propositions et essaye de les prouver. Le coeur du d´emonstrateur est la fonction examine. Partant d’une chaˆıne de caract`eres, elle la transforme en proposition, calcule ses variables libres et appelle v´erifie_tautologie. Si rien ne se passe, la proposition est un th´eor`eme et la fonction examine affiche un message pour le dire. Sinon, v´erifie_tautologie d´eclenche l’exception R´efutation et examine imprime une r´efutation prouvant que la proposition fournie n’est pas un th´eor`eme. Fichier demo.ml #open "prop";; #open "asynt";; let examine cha^ıne = let proposition = analyse_proposition cha^ıne in let variables = variables_libres proposition in try v´erifie_tautologie proposition variables; begin match variables with | [] -> 222 D´emonstration de propositions print_string "Th´eor`eme: " | [var] -> print_string ("Th´eor`eme: pour toute proposition "^var^", ") | _ -> print_string "Th´eor`eme: pour toutes propositions "; do_list (function var -> print_string (var^", ")) variables end; print_string cha^ıne; print_newline () with R´efutation liaisons -> print_string (cha^ıne ^ " n’est pas un th´eor`eme,\n"); print_string "car la proposition est fausse quand\n"; do_list (function (var, b) -> print_string (var ^ " est "); print_string (if b then "vraie" else "fausse"); print_newline ()) liaisons;; Nous mettons autour de cette fonction une boucle d’interaction standard, dans le style de celle pour le mini-Logo pr´esent´ee `a la section 10.3. Fichier demo.ml let boucle () = try while true do print_string ">>> "; examine (read_line ()) done with End_of_file -> ();; if sys__interactive then () else begin boucle (); exit 0 end;; L’identificateur sys__interactive est pr´ed´efini par le syst`eme, vaut true si le programme tourne sous le syst`eme interactif et false si le programme tourne comme une application ind´ependante. Nous le testons pour ´eviter de lancer la boucle d’interaction dans le cas o`u ce code est ex´ecut´e depuis le syst`eme interactif. Dans ce cas, il est pr´ef´erable de laisser l’utilisateur lancer la boucle lui-mˆeme et surtout de ne pas sortir pr´ematur´ement du syst`eme interactif par exit 0 quand la boucle termine. Compilation du programme Il ne reste plus qu’`a compiler les modules et `a les lier entre eux. Voici comment faire, `a gauche avec le compilateur ind´ependant camlc, `a droite avec le syst`eme interactif. $ camlc -c prop.mli # compile "prop.mli";; $ camlc -c prop.ml # compile "prop.ml";; $ camlc -c lexuniv.mli # compile "lexuniv.mli";; $ camlc -c lexuniv.ml # compile "lexuniv.ml";; $ camlc -c asynt.mli # compile "asynt.mli";; $ camlc -c asynt.ml # compile "asynt.ml";; $ camlc -c demo.ml # compile "demo.ml";; Exemples de th´eor`emes 223 $ camlc -o demo prop.zo \ # do_list load_object ["prop.zo"; lexuniv.zo asynt.zo demo.zo "lexuniv.zo";"asynt.zo";"demo.zo"];; Le d´emonstrateur se lance par l’incantation suivante : $ camlrun demo #demo__boucle ();; Nous pouvons alors taper des propositions en r´eponse au signe d’invite « >>> ». Pour sortir du programme, il faut taper un caract`ere « fin de fichier » (ctrl-D en Unix) ou une interruption (ctrl-C en Unix). 12.7 Exemples de th´eor`emes Simples mais tellement vrais Pour montrer les capacit´es de notre programme, nous ´etablissons des th´eor`emes simples, mais de moins en moins intuitifs. Tout d’abord, « le contraire du contraire d’une proposition c’est la proposition elle-mˆeme » : >>> non (non P) <=> P Th´eor`eme: pour toute proposition P, non (non P) <=> P Le tiers exclus : une proposition est toujours soit vraie soit fausse mais jamais les deux en mˆeme temps. >>> P ou (non P) <=> vrai Th´eor`eme: pour toute proposition P, P ou (non P) <=> vrai >>> P et (non P) <=> faux Th´eor`eme: pour toute proposition P, P et (non P) <=> faux Dire qu’une proposition est ´equivalente `a « vrai » c’est simplement dire que la proposition est vraie ; inversement, dire qu’elle est ´equivalente `a « faux », c’est dire que sa n´egation est vraie : >>> (P <=> vrai) <=> P Th´eor`eme: pour toute proposition P, (P <=> vrai) <=> P >>> (P <=> faux) <=> non P Th´eor`eme: pour toute proposition P, (P <=> faux) <=> non P Les deux th´eor`emes pr´ec´edents se r´e´ecrivent donc plus simplement : >>> P ou (non P) Th´eor`eme: pour toute proposition P, P ou (non P) >>> non (P et (non P)) Th´eor`eme: pour toute proposition P, non (P et (non P)) En logique, il est inutile de r´ep´eter deux fois la mˆeme chose : « P ou P » c’est P. Et inutile d’insister, « P et P » c’est aussi P. >>> P ou P <=> P Th´eor`eme: pour toute proposition P, P ou P <=> P >>> P et P <=> P Th´eor`eme: pour toute proposition P, P et P <=> P Il est ´evident qu’en supposant une hypoth`ese vraie, on la d´emontre facilement (« P ⇒ P »). De plus, une proposition est toujours ´equivalente `a elle-mˆeme. 224 D´emonstration de propositions >>> P => P Th´eor`eme: pour toute proposition P, P => P >>> P <=> P Th´eor`eme: pour toute proposition P, P <=> P On sait bien que P ⇔ Q est synonyme de Q ⇔ P, mais nous le prouvons : >>> (P <=> Q) <=> (Q <=> P) Th´eor`eme: pour toutes propositions Q, P, (P <=> Q) <=> (Q <=> P) On sait aussi que prouver l’´equivalence de deux propositions est ´equivalent `a prouver l’´equivalence de leur n´egation : >>> (non P <=> non Q) <=> (P <=> Q) Th´eor`eme: pour toutes propositions Q, P, (non P <=> non Q) <=> (P <=> Q) Mais on connaˆıt souvent moins bien la notion de proposition contrapos´ee d’une implication. La contrapos´ee de P ⇒ Q est la proposition (non Q) ⇒ (non P). Elle est int´eressante car elle est ´equivalente `a la proposition de d´epart. Cependant, il est quelquefois plus facile de prouver la contrapos´ee d’une proposition que la proposition elle-mˆeme. Nous ´etablissons le th´eor`eme : >>> (P => Q) <=> (non Q) => (non P) Th´eor`eme: pour toutes propositions Q, P, (P => Q) <=> (non Q) => (non P) La d´emonstration par l’absurde consiste, pour d´emontrer P ⇒ Q, `a supposer vraie l’hypoth`ese P et fausse la conclusion Q et `a en d´eduire une contradiction — ce qui revient `a dire qu’on en d´eduit la proposition « faux ». La validit´e de cette m´ethode de d´emonstration repose donc sur le th´eor`eme suivant : >>> (P et non Q => faux) <=> (P => Q) Th´eor`eme: pour toutes propositions Q, P, (P et non Q => faux) <=> (P => Q) La r´eponse aux questions de l’introduction Nous r´epondons maintenant aux interrogations du d´ebut de ce chapitre. Nous avions pos´e la question : « sachant que la phrase P est vraie et que la phrase Q est fausse, est-ce que la phrase obtenue en disant P et Q est une phrase vraie ? ». L’hypoth`ese « P est vraie et Q est fausse » se traduit simplement par P et non Q. Sous cette hypoth`ese, peut-on d´emontrer que P et Q est vraie ? >>> (P et non Q) => (P et Q) (P et non Q) => (P et Q) n’est pas un th´eor`eme, car la proposition est fausse quand P est vraie Q est fausse Non, la d´eduction est erron´ee. En revanche le contraire est exact : >>> (P et non Q) => non (P et Q) Th´eor`eme: pour toutes propositions Q, P, (P et non Q) => non (P et Q) Examinons maintenant la validit´e du raisonnement complexe : Exemples de th´eor`emes 225 Puisque P est vraie et que Q est fausse, P n’est donc pas une condition n´ecessaire pour que Q soit vraie La premi`ere ligne ne pose pas de probl`eme de traduction. Le raisonnement « puisque R, donc S » se traduit ais´ement : c’est une autre fa¸con d’exprimer que l’implication «R ⇒ S » est vraie. Le raisonnement se traduit donc par (P et non Q) ⇒ . . . En revanche, que signifie « condition n´ecessaire » ? P est une condition n´ecessaire pour Q si, d`es que Q est vraie, alors P est elle aussi vraie. Autrement dit, si Q est vraie, P est n´ecessairement vraie. Cela signifie que l’on a Q ⇒ P. La phrase « P n’est pas une condition n´ecessaire pour que Q soit vraie » signifie donc simplement non (Q ⇒ P). En mettant les morceaux ensemble, on obtient : >>> (P et non Q) => (non (Q => P)) (P et non Q) => (non (Q => P)) n’est pas un th´eor`eme, car la proposition est fausse quand P est vraie Q est fausse Le raisonnement n’´etait pas valide. On a confondu condition n´ecessaire et condition suffisante : une condition suffisante pour qu’une proposition Q soit vraie est une proposition P qui permet de d´eduire la proposition Q. Autrement dit, P est une condition suffisante pour que Q soit vraie si P suffit pour d´emontrer Q, c’est-`a-dire si P ⇒ Q est vraie. Sous les hypoth`eses du raisonnement, il est exact que P n’est pas une condition suffisante pour d´emontrer Q. En effet : >>> (P et non Q) => (non (P => Q)) Th´eor`eme: pour toutes propositions Q, P, (P et non Q) => (non (P => Q)) Lois de De Morgan Nous d´emontrons maintenant deux th´eor`emes, bien connus en logique sous le nom de lois de De Morgan, qui font le lien entre les connecteurs « et », « ou » et « non » : >>> non (P et Q) <=> (non P) ou (non Q) Th´eor`eme: pour toutes propositions Q, P, non (P et Q) <=> (non P) ou (non Q) >>> non (P ou Q) <=> (non P) et (non Q) Th´eor`eme: pour toutes propositions Q, P, non (P ou Q) <=> (non P) et (non Q) Les lois de De Morgan sont quelquefois utiles en informatique. Par exemple, dans une alternative comme if not (x <= 1 || x >= 2) then ... else ... on simplifie la condition en employant les lois de De Morgan. En effet, not (x <= 1 || x >= 2) signifie (not (x <= 1)) && (not (x >= 2)) c’est-`a-dire x > 1 && x < 2. (Il faut savoir que le contraire de <= est >, celui de >= est < et r´eciproquement.) Remarquez que nous pouvons d´emontrer : >>> non ((non P) ou (non Q)) <=> P et Q Th´eor`eme: pour toutes propositions Q, P, non ((non P) ou (non Q)) <=> P et Q 226 D´emonstration de propositions Cette propri´et´e permet de r´eduire plus rapidement l’alternative ci-dessus. En remarquant que x <= 1 est ´equivalent `a not (x > 1) et que x >= 2 est ´equivalent `a not (x < 2), on ´ecrit la condition not (x <= 1 || x >= 2) sous la forme not ((not (x > 1)) || (not (x < 2))). Il ne reste qu’`a utiliser le th´eor`eme pr´ec´edent avec P = (x > 1) et Q = (x < 2). On obtient alors P et Q, c’est-`a-dire x > 1 && x < 2. Si vous ne vous int´eressez pas aux propri´et´es alg´ebriques des connecteurs logiques, ou si vous n’ˆetes pas curieux de voir notre programme d´emontrer des propri´et´es « abstraites », vous pouvez passer `a la section suivante. Propri´et´es alg´ebriques des connecteurs propositionnels On ´etablit en math´ematiques que le « et » et le « ou » sont des op´erations associatives et commutatives. Ces propri´et´es sont communes aux connecteurs propositionnels et aux op´erations arithm´etiques + et ×. Par exemple, pour l’addition, la commutativit´e signifie qu’on peut additionner les nombres dans n’importe quel ordre sans changer le r´esultat : x + y = y + x. L’associativit´e concerne les parenth`eses ; elle indique simplement que la place des parenth`eses ne change pas le r´esultat d’une addition, ce qu’on exprime par une formule qui montre que d´eplacer les parenth`eses ne modifie pas le r´esultat du calcul : (x + y) + z = x + (y + z). Le « ou » et le « et » v´erifient ces propri´et´es : >>> (P ou Q) <=> (Q ou P) Th´eor`eme: pour toutes propositions Q, P, (P ou Q) <=> (Q ou P) >>> ((P ou Q) ou R) <=> (P ou (Q ou R)) Th´eor`eme: pour toutes propositions R, Q, P, ((P ou Q) ou R) <=> (P ou (Q ou R)) L’implication est-elle aussi associative et commutative ? >>> (P => Q) <=> (Q => P) (P => Q) <=> (Q => P) n’est pas un th´eor`eme, car la proposition est fausse quand P est fausse Q est vraie >>> ((P => Q) => R) <=> (P => (Q => R)) ((P => Q) => R) <=> (P => (Q => R)) n’est pas un th´eor`eme, car la proposition est fausse quand P est fausse Q est vraie R est fausse Une propri´et´e int´eressante : une combinaison de « et » et de « ou » se comporte comme la multiplication et l’addition, on la « d´eveloppe » de fa¸con analogue. Il est bien connu que la multiplication est distributive par rapport `a l’addition, ce qui permet d’´ecrire : x × (y + z) = x × y + x × z. Notre programme prouve que le « et » est distributif par rapport au « ou », c’est-`a-dire qu’on d´eveloppe P et (Q ou R) comme si c’´etait P × (Q + R) : >>> (P et (Q ou R)) <=> (P et Q) ou (P et R) Th´eor`eme: pour toutes propositions R, Q, P, (P et (Q ou R)) <=> (P et Q) ou (P et R) Exemples de th´eor`emes 227 Il d´emontre tout aussi facilement que le « ou » est distributif par rapport au « et » (ce r´esultat n’a pas d’analogue en arithm´etique). >>> (P ou (Q et R)) <=> (P ou Q) et (P ou R) Th´eor`eme: pour toutes propositions R, Q, P, (P ou (Q et R)) <=> (P ou Q) et (P ou R) Imaginez ce que serait une d´emonstration « `a la main » de cette propri´et´e avec des tables de v´erit´e : on aurait huit lignes et huit colonnes . . . Finalement, l’´equivalence est une relation d’´equivalence, ce qui signifie simplement qu’elle introduit une certaine notion d’´egalit´e. Il est ´evidemment heureux qu’il en soit ainsi : il serait vraiment dommage que l’´equivalence ne soit pas une relation d’´equivalence ! De plus, nous avons vu que l’´equivalence d´efinissait la notion de propositions logiquement identiques, c’est-`a-dire ´egales au point de vue de la logique ; l’´equivalence d´efinit donc bien une notion d’´egalit´e. Formellement, une relation d’´equivalence est une relation r´eflexive, sym´etrique et transitive. L’´egalit´e en math´ematiques (le symbole =) a ces propri´et´es et ce sont les propri´et´es minimales qu’on doit exiger d’une relation pour qu’elle d´efinisse l’id´ee de deux objets semblables. La principale difficult´e pour expliciter ces propri´et´es provient de leur ´evidence mˆeme : nous y sommes tellement habitu´es et elles paraissent tellement ´evidentes que « ¸ca va sans dire ». C’est exactement le but de la logique que d’´ecrire noir sur blanc ces ´evidences. La r´eflexivit´e signifie qu’un objet est toujours ´egal `a lui-mˆeme : x = x. La sym´etrie signifie que lorsqu’un objet est ´egal `a un autre objet, l’autre objet est aussi ´egal au premier : si x = y alors y = x. La transitivit´e se traduit par « deux objets ´egaux `a un mˆeme troisi`eme sont ´egaux entre eux », ou encore si x = y et y = z alors x = z. Le programme ´etablit pour nous ces trois propri´et´es de l’´equivalence logique : >>> P <=> P Th´eor`eme: pour toute proposition P, P <=> P >>> (P <=> Q) => (Q <=> P) Th´eor`eme: pour toutes propositions Q, P, (P <=> Q) => (Q <=> P) >>> (P <=> Q) et (Q <=> R) => (P <=> R) Th´eor`eme: pour toutes propositions R, Q, P, (P <=> Q) et (Q <=> R) => (P <=> R) On utilise les deux th´eor`emes suivants pour simplifier les circuits logiques. Le premier permet d’´eliminer deux connecteurs propositionnels : >>> (P ou (P et Q)) <=> P Th´eor`eme: pour toutes propositions Q, P, (P ou (P et Q)) <=> P On supprime de mˆeme un connecteur et une n´egation grˆace au th´eor`eme : >>> (P ou (non P et Q)) <=> P ou Q Th´eor`eme: pour toutes propositions Q, P, (P ou (non P et Q)) <=> P ou Q Pour finir, un petit exercice (le premier exemple s’appelle la loi de Pierce) : >>> ((P => Q) => P) => P Th´eor`eme: pour toutes propositions Q, P, ((P => Q) => P) => P >>> ((P => Q) => P) => Q ((P => Q) => P) => Q n’est pas un th´eor`eme, car la proposition est fausse quand 228 D´emonstration de propositions P est vraie Q est fausse >>> (P <=> Q) <=> ((P ou Q) => (P et Q)) Th´eor`eme: pour toutes propositions Q, P, (P <=> Q) <=> ((P ou Q) => (P et Q)) L’auriez-vous devin´e en r´efl´echissant au sens des connecteurs ? 12.8 Pour aller plus loin : l’analyseur lexical universel Dans cette section, nous impl´ementons l’analyseur lexical « universel » utilis´e pour lire les propositions. On rappelle l’interface de ce module : Fichier lexuniv.mli type lex`eme = | MC of string | Ident of string | Entier of int;; value construire_analyseur: string list -> (char stream -> lex`eme stream);; L’impl´ementation reprend de gros morceaux de l’analyseur lexical ´ecrit pour le mini- Logo (chapitre 9, section 9.6), en les rendant un peu plus g´en´eraux. La principale nouveaut´e est l’introduction de tables de hachage pour d´ecider rapidement si un identificateur est un mot-cl´e. Les analyseurs lexicaux engendr´es par la fonction construire_analyseur savent reconnaˆıtre les nombres entiers, les identificateurs et les mots-cl´es. Il serait facile d’ajouter les nombres flottants et les chaˆınes de caract`eres, mais cela n’est pas utile pour l’utilisation que nous en ferons. Fonctions de lecture de base Pour la lecture des entiers et des identificateurs, nous r´eutilisons les fonctions lire_entier et lire_mot du mini-Logo. Fichier lexuniv.ml let rec lire_entier accumulateur flux = match flux with | [< ’(‘0‘..‘9‘ as c) >] -> lire_entier (10 * accumulateur + int_of_char c - 48) flux | [< >] -> accumulateur;; let tampon = make_string 16 ‘-‘;; let rec lire_mot position flux = match flux with | [< ’(‘A‘..‘Z‘ | ‘a‘..‘z‘ | ‘0‘..‘9‘ | ‘_‘ | ‘’‘ | ‘´e‘|‘`a‘|‘`e‘|‘`u‘|‘^a‘|‘^e‘|‘^ı‘|‘^o‘|‘^u‘|‘¨e‘|‘¨ı‘|‘¨u‘|‘¸c‘| ‘´E‘|‘`A‘|‘`E‘|‘`U‘|‘^A‘|‘^E‘|‘^I‘|‘^O‘|‘^U‘|‘¨E‘|‘¨I‘|‘¨U‘|‘C¸‘ as c) >] -> Pour aller plus loin : l’analyseur lexical universel 229 if position < string_length tampon then tampon.[position] <- c; lire_mot (position + 1) flux | [< >] -> sub_string tampon 0 (min position (string_length tampon));; Sur le mod`ele de lire_mot, nous ajoutons une fonction lire_symbole qui reconnaˆıt les suites de caract`eres sp´eciaux, comme ** ou <=>. Ces suites de caract`eres sp´eciaux sont soit des mots-cl´es soit des identificateurs, exactement comme les suites de lettres et de chiffres qui constituent un mot. Fichier lexuniv.ml let rec lire_symbole position flux = match flux with | [< ’(‘!‘|‘$‘|‘%‘|‘&‘|‘*‘|‘+‘|‘-‘|‘.‘|‘/‘|‘:‘| ‘;‘|‘<‘|‘=‘|‘>‘|‘?‘|‘@‘|‘^‘|‘|‘|‘~‘ as c) >] -> if position < string_length tampon then tampon.[position] <- c; lire_symbole (position + 1) flux | [< >] -> sub_string tampon 0 (min position (string_length tampon));; L’analyseur lexical autorise des commentaires dans le texte d’entr´ee, sous une forme tr`es simple : tout ce qui suit un caract`ere # est ignor´e, jusqu’`a la fin de la ligne. La fonction lire_commentaire se charge de sauter tous les caract`eres du flux d’entr´ee jusqu’au prochain caract`ere de fin de ligne. Fichier lexuniv.ml let rec lire_commentaire flux = match flux with | [< ’‘\n‘ >] -> () | [< ’c >] -> lire_commentaire flux;; Recherche des mots-cl´es par hachage Ayant reconnu un mot ou un symbole, il reste `a d´eterminer s’il appartient `a la liste des mots-cl´es. Ce test sera fait une fois pour chaque mot ou symbole du flux d’entr´ee, donc un tr`es grand nombre de fois ; il est par cons´equent crucial que ce test « aille vite ». Au lieu d’une simple recherche lin´eaire dans la liste des mots-cl´es, nous employons une technique plus subtile, connue sous le nom de hachage, et la structure de donn´ees correspondante, les tables de hachage. La technique du hachage est d´ecrite en d´etail `a la prochaine section. Pour l’instant, nous allons juste expliquer le comportement d’une table de hachage. Une table de hachage est une table d’association physiquement modifiable : elle enregistre des associations de certaines cl´es avec certaines donn´ees ; on entre de nouvelles associations, ou l’on en retire d’anciennes en modifiant physiquement la table. Voici une partie de l’interface du module hashtbl de la biblioth`eque standard, qui impl´emente les tables de hachage : type (’a, ’b) t;; value new: int -> (’a, ’b) t 230 D´emonstration de propositions and add: (’a, ’b) t -> ’a -> ’b -> unit and find: (’a, ’b) t -> ’a -> ’b;; Le type (’a, ’b) hashtbl__t est le type des tables de hachage associant des cl´es de type ’a avec des donn´ees de type ’b. La fonction hashtbl__new renvoie une nouvelle table de hachage, initialement vide, c’est-`a-dire ne contenant aucune liaison. (Le param`etre entier de hashtbl__new est une indication de la taille de la table ; nous verrons son rˆole dans la prochaine section.) La fonction hashtbl__find effectue une recherche dans une table de hachage : hashtbl__find t c renvoie la donn´ee `a laquelle la cl´e c est li´ee dans la table t, ou d´eclenche l’exception Not_found si la cl´e c n’est pas li´ee. La fonction hashtbl__add enregistre une liaison dans une table de hachage : hashtbl__add t c d place dans la table t une liaison de la cl´e c `a la donn´ee d. Si la cl´e c ´etait d´ej`a li´ee `a une donn´ee, la nouvelle liaison cache l’ancienne. Pour r´esumer, disons que les tables de hachage se comportent exactement comme des listes d’association physiquement modifiables. Poussant cette intuition, on peut tr`es bien faire une impl´ementation correcte du module hashtbl `a l’aide de listes d’association, sans employer la technique du hachage : type (’a, ’b) t == (’a * ’b) list ref;; let new n = ref [];; let add t c d = t := (c, d) :: !t;; let find t c = assoc c !t;; La vraie impl´ementation de hashtbl, esquiss´ee dans la prochaine section, est bien plus complexe : elle utilise du hachage pour acc´el´erer consid´erablement l’op´eration find. Cependant, son comportement est exactement le mˆeme que celui de l’impl´ementation na¨ıve donn´ee ci-dessus. Retenons donc qu’une table de hachage se comporte comme une liste d’association, `a ceci pr`es que les op´erations de recherche sont beaucoup plus efficaces. Nous utiliserons donc une table de hachage pour stocker l’ensemble des motscl ´es d’un analyseur lexical. La table associe aux mots-cl´es eux-mˆemes (des chaˆınes de caract`eres) les lex`emes correspondants. Elle est donc du type (string, lex`eme) hashtbl__t. Pour d´eterminer si un mot trouv´e dans le flux d’entr´ee est un mot-cl´e ou un simple identificateur, on interroge tout simplement la table des mots-cl´es avec hashtbl__find. Fichier lexuniv.ml let mc_ou_ident table_des_mots_cl´es ident = try hashtbl__find table_des_mots_cl´es ident with Not_found -> Ident ident;; Une variante de mc_ou_ident nous sera utile pour reconnaˆıtre les mots-cl´es monocaract` eres, par exemple les parenth`eses. Fichier lexuniv.ml let mc_ou_erreur table_des_mots_cl´es caract`ere = let ident = make_string 1 caract`ere in try hashtbl__find table_des_mots_cl´es ident with Not_found -> raise Parse_error;; Pour aller plus loin : l’analyseur lexical universel 231 Reconnaissance d’un lex`eme La lecture des lex`emes consiste tout d’abord `a passer les blancs et les commentaires, puis `a reconnaˆıtre un identificateur, un mot-cl´e ou un nombre entier (´eventuellement n´egatif, donc pr´ec´ed´e du signe « - »). Les symboles mono-caract`eres comme les parenth` eses ()[]{} ou bien les caract`eres non imprimables doivent ˆetre d´eclar´es comme mots-cl´es, sinon ils produisent une erreur. Fichier lexuniv.ml let rec lire_lex`eme table flux = match flux with | [< ’(‘ ‘ | ‘\n‘ | ‘\r‘ | ‘\t‘) >] -> lire_lex`eme table flux | [< ’‘#‘ >] -> lire_commentaire flux; lire_lex`eme table flux | [< ’(‘A‘..‘Z‘ | ‘a‘..‘z‘ | ‘´e‘|‘`a‘|‘`e‘|‘`u‘|‘^a‘|‘^e‘|‘^ı‘|‘^o‘|‘^u‘|‘¨e‘|‘¨ı‘|‘¨u‘|‘¸c‘| ‘´E‘|‘`A‘|‘`E‘|‘`U‘|‘^A‘|‘^E‘|‘^I‘|‘^O‘|‘^U‘|‘¨E‘|‘¨I‘|‘¨U‘|‘C¸‘ as c) >] -> tampon.[0] <- c; mc_ou_ident table (lire_mot 1 flux) | [< ’(‘!‘|‘$‘|‘%‘|‘&‘|‘*‘|‘+‘|‘.‘|‘/‘|‘:‘|‘;‘| ‘<‘|‘=‘|‘>‘|‘?‘|‘@‘|‘^‘|‘|‘|‘~‘ as c) >] -> tampon.[0] <- c; mc_ou_ident table (lire_symbole 1 flux) | [< ’(‘0‘..‘9‘ as c) >] -> Entier(lire_entier (int_of_char c - 48) flux) | [< ’‘-‘ >] -> begin match flux with | [< ’(‘0‘..‘9‘ as c) >] -> Entier(- (lire_entier (int_of_char c - 48) flux)) | [< >] -> tampon.[0] <- ‘-‘; mc_ou_ident table (lire_symbole 1 flux) end | [< ’c >] -> mc_ou_erreur table c;; G´en´eration de l’analyseur Comme pour le mini-Logo, on construit le flux des lex`emes par appels r´ep´et´es `a lire_lex`eme. Fichier lexuniv.ml let rec analyseur table flux = stream_from (function () -> match flux with | [< (lire_lex`eme table) lex`eme >] -> lex`eme | [< >] -> raise Parse_failure);; 232 D´emonstration de propositions Finalement, la g´en´eration d’un analyseur lexical consiste simplement `a construire sa table des mots-cl´es, puis `a appliquer partiellement l’analyseur g´en´erique `a cette table. Le r´esultat de l’application partielle est la fonction des flux de caract`eres vers les flux de lex`emes d´esir´ee. Fichier lexuniv.ml let construire_analyseur mots_cl´es = let table_des_mots_cl´es = hashtbl__new 17 in do_list (function mot -> hashtbl__add table_des_mots_cl´es mot (MC mot)) mots_cl´es; analyseur table_des_mots_cl´es;; 12.9 Pour aller encore plus loin : le hachage L’id´ee du hachage vient de la constatation que la recherche d’un objet dans une liste d’association se r´ev`ele coˆuteuse si l’on doit faire beaucoup de recherches, en particulier si ces recherches sont la plupart du temps infructueuses. En effet pour trouver un objet dans une liste d’association, il faut en moyenne parcourir la moiti´e de la liste, si l’on suppose qu’on recherche des cl´es en moyenne dispos´ees au hasard dans la liste. Pour constater que la cl´e est absente de la liste, c’est pire : il faut parcourir toute la liste. (C’est le cas le plus fr´equent dans l’exemple de l’analyse lexicale.) Le seul moyen d’acc´el´erer la recherche d’une cl´e dans un ensemble est d’´eliminer tr`es rapidement un grand nombre de tests en prouvant tr`es vite qu’ils sont vou´es `a l’´echec. En particulier, on ira beaucoup plus vite si l’on est capable de restreindre la recherche exhaustive de la cl´e `a un ensemble beaucoup plus petit que celui de d´epart. C’est toujours le principe de « diviser pour r´egner » qui pr´evaut. Le hachage consiste donc `a fractionner un gros ensemble de cl´es en sous-ensembles coh´erents et `a ne chercher une cl´e que dans le petit sous-ensemble qui la concerne. La m´ethode suppose donc qu’on dispose d’un moyen tr`es rapide de d´eterminer le sousensemble auquel une cl´e est susceptible d’appartenir. Les sous-ensembles sont par exemple de simples listes ; on les regroupe en un tableau, afin de pouvoir acc´eder directement `a n’importe quel sous-ensemble. On repr´esente donc le sous-ensemble auquel appartient une cl´e par un simple num´ero, l’indice du sous-ensemble dans le tableau des sous-ensembles. La fonction qui d´etermine ce num´ero s’appelle justement la fonction de hachage. En termes savants, on dit qu’on partitionne les cl´es en classes d’´equivalence modulo la fonction de hachage. En termes simples, on range les cl´es dans le mˆeme sous-ensemble quand elles ont la mˆeme image par la fonction de hachage. Prenons un exemple tr`es simple : supposons que les cl´es soient des nombres entiers. Comment partager rapidement ces entiers en dix sous-ensembles ? Il suffit de regarder leur dernier chiffre. Si l’on veut les partager en deux sous-ensembles, on consid`ere leur parit´e (pair ou impair). De fa¸con g´en´erale, une mani`ere de les partager en n sous-ensembles est de calculer le reste de leur division par n. `A titre d´emonstratif, nous prenons dix sous-ensembles. Notre fonction de hachage est donc : # let hache cl´e = cl´e mod 10;; Pour aller encore plus loin : le hachage 233 hache : int -> int = Maintenant, nous voulons associer des informations `a nos entiers, par exemple des chaˆınes de caract`eres. C’est notamment le cas si nous voulons repr´esenter un annuaire « `a l’envers » : `a partir d’un num´ero de t´el´ephone, nous d´esirons retrouver le nom du correspondant. Sous forme de liste d’association, cela donne : # let liste_d’association = [11, "police"; 16, "pompiers"; 0139635511, "standard"; 0139635198, "Pierre"; 0139635202, "Xavier"; 7234864, "Xavier"; 0139635570, "Nelly"; 3613, "T´el´etel 1"; 3615, "T´el´etel 3" ];; Sous forme de table de hachage, nous divisons cette liste en dix listes d’association, suivant le dernier chiffre du num´ero : # let table_des_sous_ensembles = [| (* 0 *) [0139635570, "Nelly"]; (* 1 *) [11, "police"; 0139635511, "standard"]; (* 2 *) [0139635202, "Xavier"]; (* 3 *) [3613, "T´el´etel 1"]; (* 4 *) [7234864, "Xavier"]; (* 5 *) [3615, "T´el´etel 3"]; (* 6 *) [16, "pompiers"]; (* 7 *) []; (* 8 *) [0139635198, "Pierre"]; (* 9 *) [] |];; Pour trouver le sous-ensemble dans lequel chercher une cl´e, on cherche son num´ero en « hachant » la cl´e puis on extrait du tableau le sous-ensemble concern´e. Pour chercher l’associ´e d’une cl´e on utilise simplement assoc sur le sous-ensemble correspondant `a la cl´e. # let sous_ensemble_de cl´e = let num´ero_du_sous_ensemble = hache cl´e in table_des_sous_ensembles.(num´ero_du_sous_ensemble);; sous_ensemble_de : int -> (int * string) list = # let associ´e_de cl´e = assoc cl´e (sous_ensemble_de cl´e);; associ´e_de : int -> string = # associ´e_de 3615;; - : string = "T´el´etel 3" # associ´e_de 911;; Exception non rattrap´ee: Not_found Chaque appel `a associ´e_de finit donc par appeler la fonction assoc, mais sur des listes d’association beaucoup plus petites que la liste repr´esentant tout l’annuaire : un et deux ´el´ements, respectivement, au lieu de neuf. Dans certains cas, on tombe mˆeme imm´ediatement sur un sous-ensemble vide, par exemple si on cherche un num´ero se terminant par 7, ce qui fait que la recherche est quasi imm´ediate. Dans tous les cas, on restreint nettement l’espace de recherche. Il est facile de construire les sous-ensembles automatiquement. On part d’une table o`u tous les sous-ensembles sont vides. # let table_des_sous_ensembles = (make_vect 10 [] : (int * string) list vect);; 234 D´emonstration de propositions Puis on range chaque paire (cl´e, valeur) `a m´emoriser dans le sous-ensemble correspondant `a la valeur de hachage de la cl´e. # let ajoute_une_cl´e ((cl´e, valeur) as cl´e_valeur) = let num´ero_du_sous_ensemble = hache cl´e in table_des_sous_ensembles.(num´ero_du_sous_ensemble) <- cl´e_valeur :: table_des_sous_ensembles.(num´ero_du_sous_ensemble);; ajoute_une_cl´e : int * string -> unit = # do_list ajoute_une_cl´e liste_d’association;; - : unit = () # table_des_sous_ensembles;; - : (int * string) list vect = [|[139635570, "Nelly"]; [139635511, "standard"; 11, "police"]; [139635202, "Xavier"]; [3613, "T´el´etel 1"]; [7234864, "Xavier"]; [3615, "T´el´etel 3"]; [16, "pompiers"]; []; [139635198, "Pierre"]; []|] Le hachage n’est pas restreint aux cl´es de type entier. On peut l’appliquer `a n’importe quel type de cl´es, pourvu qu’on sache associer rapidement un entier `a une cl´e. On d´efinit alors la fonction de hachage comme ´etant l’entier associ´e `a la cl´e, modulo la taille de la table de hachage. La transformation de la cl´e en entier n’a pas besoin d’ˆetre « exacte », en ce sens que deux cl´es diff´erentes ont sans probl`eme le mˆeme entier associ´e. Pour obtenir une bonne r´epartition des cl´es dans les sous-ensembles, il faut quand mˆeme s’efforcer d’´eviter autant que possible cette situation. Dans le cas particulier o`u les cl´es sont des chaˆınes de caract`eres, cas d’une grande importance pratique, on a propos´e un certain nombre de « recettes » pour associer rapidement un entier `a une chaˆıne, avec de bonnes propri´et´es de r´epartition. Voici un exemple simple de fonction de hachage sur les chaˆınes : # let hache_cha^ıne taille_table c = let res = ref 0 in for i = 0 to string_length c - 1 do res := (int_of_char c.[i] + !res * 128) mod taille_table done; !res;; hache_cha^ıne : int -> string -> int = L’id´ee est de faire intervenir dans le r´esultat final la valeur de chacun des caract`eres de la chaˆıne, pour assurer une bonne dispersion des r´esultats. Nous n’essaierons pas de justifier la formule ci-dessus dans tous ses d´etails (pourquoi 128, etc.). Nous savons donc hacher des entiers et des chaˆınes. Le syst`eme Caml va beaucoup plus loin que cela : il fournit une fonction de biblioth`eque capable d’associer un entier `a n’importe quelle valeur Caml, quel que soit son type. Il s’agit de la fonction hashtbl__hash, de type ’a -> int. Cette fonction est raisonnablement rapide et produit des r´esultats assez bien r´epartis. Au-dessus de cette fonction, il est facile de d´efinir une fonction de hachage qui op`ere sur tous les types de cl´es : # let hache taille_table cl´e = hashtbl__hash cl´e mod taille_table;; hache : int -> ’a -> int = Pour aller encore plus loin : le hachage 235 Ensuite, on construit facilement un type t et des op´erations new, add et find comparables `a ceux du module hashtbl : # type (’a, ’b) t == (’a * ’b) list vect;; Le type t est d´efini. # let new taille_table = make_vect taille_table [];; new : int -> ’a list vect = # let add table cl´e donn´ee = let index = hache (vect_length table) cl´e in table.(index) <- (cl´e, donn´ee) :: table.(index);; add : (’a * ’b) list vect -> ’a -> ’b -> unit = # let find table cl´e = let index = hache (vect_length table) cl´e in assoc cl´e table.(index);; find : (’a * ’b) list vect -> ’a -> ’b = L’impl´ementation du module hashtbl fournie par la biblioth`eque standard s’appuie elle aussi sur la fonction hash polymorphe, mais est plus complexe que l’impl´ementation donn´ee ci-dessus. En particulier, elle sait agrandir dynamiquement la table quand les sous-ensembles menacent de devenir trop gros, ce qui garantit de bonnes performances mˆeme sur de tr`es grosses tables. 13 Compression de fichiers O`u l’on fait passer un chameau par le chas d’une aiguille. ans ce chapitre, nous programmerons une commande de compression de fichiers. La compression consiste `a transformer des fichiers pour qu’ils occupent moins de place ; l’op´eration inverse, la d´ecompression, reconstruit les fichiers de d´epart `a partir des fichiers transform´es. Ce sera l’occasion d’introduire quelques algorithmes classiques, en particulier deux exemples int´eressants d’utilisation des arbres binaires, parmi bien d’autres. Nous aurons ´egalement besoin de faire des entr´ees-sorties bit par bit, et donc de manipuler les entiers au niveau du bit. 13.1 La compression de donn´ees La plupart des fichiers stock´es dans les ordinateurs contiennent un certain degr´e de redondance. Tr`es souvent, si l’on code diff´eremment les donn´ees qu’ils contiennent, on r´eduit consid´erablement leur taille, sans perte d’information, si l’on suppose ´evidemment que le processus de recodage est r´eversible, et qu’il permet donc de retrouver les fichiers d’origine `a tout instant. C’est ce recodage qu’on appelle compression des donn´ees. Les proc´ed´es de compression et de d´ecompression de donn´ees sont de plus en plus employ´es dans les environnements informatiques : en premier lieu dans des programmes utilitaires sp´ecialis´es comme gzip, stuffit ou pkzip, qui souvent combinent compression et archivage (regroupement d’une hi´erarchie de fichiers en un seul fichier) ; mais aussi dans certains pilotes de disques, qui compressent “au vol” les donn´ees avant de les ´ecrire sur le disque, augmentant ainsi la capacit´e apparente de ce dernier ; et mˆeme dans l’´electronique des modems, qui compressent “au vol” (en temps r´eel) les donn´ees transmises sur la ligne t´el´ephonique, augmentant ainsi le d´ebit des transmissions. En guise d’exemple tr`es simple d’algorithme de compression, mentionnons la m´ethode dite run-length encoding, qui consiste `a repr´esenter toute s´equence de n fois le mˆeme octet c par un code sp´ecial signifiant “r´ep´etition”, suivi de l’octet c, suivi du nombre de r´ep´etitions n. Ce codage est plus compact que l’original d`es que n est plus grand que 4. Il est int´eressant sur certains types de fichiers, comme les sorties 238 Compression de fichiers pour l’imprimante en informatique de gestion, qui comportent de longues s´equences de blancs (pour aligner) et de tirets (pour tracer des traits). Cependant, il est `a peu pr`es inefficace sur d’autres types de fichiers, comme les textes fran¸cais ou les fichiers de code ex´ecutable. L’algorithme de compression que nous utilisons dans ce chapitre, l’algorithme de Huffman, est plus compliqu´e, mais plus efficace car il n’est pas limit´e `a une classe particuli`ere de donn´ees. Sur des fichiers de texte fran¸cais, il atteint une r´eduction de taille d’environ 35 % en moyenne. Les meilleurs programmes de compression d´epassent 60 %, mais ils utilisent des algorithmes encore plus complexes. 13.2 Plan du programme Nous allons programmer la commande compr qui compresse les donn´ees des fichiers qu’elle traite. Les fichiers compress´es sont renomm´es en ajoutant le suffixe .cpr `a leur nom. Quand on l’appelle avec l’option -d, la commande compr d´ecompresse les fichiers qu’on lui donne en argument. Nous commen¸cons par une fonction commune `a la compression et `a la d´ecompression, qui se charge d’ouvrir les fichiers et d’afficher les erreurs d’entr´ee-sortie. Cette tˆache conceptuellement simple est en pratique fort encombr´ee par la r´ecup´eration et l’affichage des erreurs ; c’est le prix `a payer pour obtenir des programmes qui r´eagissent bien face aux situations exceptionnelles. Fichier compr.ml #open "sys";; exception Erreur;; let traite_fichier traitement nom_entr´ee nom_sortie = let entr´ee = try open_in_bin nom_entr´ee with Sys_error message -> prerr_endline ("Erreur `a l’ouverture de " ^ nom_entr´ee ^ " : " ^ message); raise Erreur in let sortie = try open_out_bin nom_sortie with Sys_error message -> close_in entr´ee; prerr_endline ("Erreur `a la cr´eation de " ^ nom_sortie ^ " : " ^ message); raise Erreur in try traitement entr´ee sortie; close_in entr´ee; close_out sortie; remove nom_entr´ee with Sys_error message -> close_in entr´ee; close_out sortie; remove nom_sortie; prerr_endline ("Erreur pendant le traitement de " ^ nom_entr´ee ^ " : " ^ message); raise Erreur;; Plan du programme 239 La fonction commence par ouvrir un canal d’entr´ee et un canal de sortie sur les fichiers indiqu´es, au moyen des fonctions open_in_bin et open_out_bin. Les fonctions de biblioth`eque open_in_bin et open_out_bin ouvrent les canaux en mode “binaire”, garantissant que les caract`eres lus ou ´ecrits sur le canal sont exactement ceux que contiennent le fichier. Au contraire, les fonctions open_in et open_out ouvrent les canaux en mode “texte” ; sur certaines impl´ementations de Caml Light, il se produit alors des traductions (en particulier sur les caract`eres de fin de lignes) au moment de la lecture et de l’´ecriture. Les fichiers `a compresser ne contiennent pas forc´ement du texte ; il est donc n´ecessaire d’op´erer en mode “binaire” pour ˆetre certain de retrouver les fichiers `a l’identique apr`es une compression suivie d’une d´ecompression. La partie difficile du travail, c’est-`a-dire la compression proprement dite, est assur´ee par la fonction traitement pass´ee en argument `a traite_fichier. Cette fonction re¸coit un canal ouvert sur l’entr´ee et un canal ouvert sur la sortie. Elle est cens´ee compresser ou d´ecompresser son entr´ee sur sa sortie. Lorsque la compression s’ach`eve sans erreur, on ferme les canaux d’entr´ee et de sortie (fonctions close_in et close_out) et on efface le fichier d’entr´ee (fonction remove du module sys). Aux yeux de l’utilisateur, tout se passe comme si on avait remplac´e le fichier d’entr´ee par le fichier de sortie. Toutes les fonctions d’entr´ee-sortie d´eclenchent l’exception Sys_error (du module sys) quand une erreur se produit, avec un message explicatif en argument de l’exception. On intercepte donc cette exception, et on affiche un message sur la sortie d’erreur standard du processus. La fonction prerr_endline ´ecrit une chaˆıne de caract`eres suivie d’un retour `a la ligne sur la sortie d’erreur standard. En cas d’erreur, on d´etruit le fichier de sortie s’il a d´ej`a ´et´e cr´e´e, et on d´eclenche l’exception Erreur. On prend bien soin de fermer les canaux quand on n’en a plus besoin, y compris en cas d’erreurs. Ce n’est pas uniquement par souci d’´el´egance : les syst`emes d’exploitation limitent le nombre de canaux d’entr´ees-sorties simultan´ement ouverts. Si on oublie de fermer les canaux inutilis´es, on se trouve vite en situation de p´enurie de canaux. Nous allons maintenant utiliser deux fois la fonction traite_fichier, pour d´efinir les fonctions de compression et de d´ecompression d’un fichier. Fichier compr.ml let compresse_fichier nom_fichier = traite_fichier huffman__compresse nom_fichier (nom_fichier ^ ".cpr");; let d´ecompresse_fichier nom_fichier = let longueur = string_length nom_fichier in if longueur < 4 || sub_string nom_fichier (longueur - 4) 4 <> ".cpr" then let nom_entr´ee = nom_fichier ^ ".cpr" and nom_sortie = nom_fichier in traite_fichier huffman__d´ecompresse nom_entr´ee nom_sortie else let nom_entr´ee = nom_fichier and nom_sortie = sub_string nom_fichier 0 (longueur - 4) in traite_fichier huffman__d´ecompresse nom_entr´ee nom_sortie;; Dans le cas de la d´ecompression, le nom du fichier compress´e peut ˆetre donn´e avec 240 Compression de fichiers ou sans l’extension .cpr. Si le nom fourni ne se termine pas par l’extension .cpr, c’est en fait le nom du fichier de sortie ; on lui ajoute .cpr pour obtenir le vrai nom du fichier d’entr´ee. Si le nom fourni se termine par l’extension .cpr, on l’enl`eve (par un sub_string bien calcul´e) pour obtenir le nom du fichier de sortie. Les deux fonctions huffman__compresse et huffman__d´ecompresse proviennent du module huffman, que nous ´etudierons en d´etail dans la prochaine section. Pour l’instant, contentons-nous de l’interface de ce module : Fichier huffman.mli value compresse : in_channel -> out_channel -> unit and d´ecompresse : in_channel -> out_channel -> unit;; Le point d’entr´ee dans le programme reconnaˆıt l’option -d et applique `a bon escient les fonctions compresse_fichier ou d´ecompresse_fichier `a chaque argument fourni sur la ligne de commande. Les arguments donn´es `a une commande sont accessibles dans le tableau de chaˆınes de caract`eres command_line, du module de biblioth`eque sys. L’´el´ement d’indice z´ero contient le nom d’appel de la commande ; les ´el´ements suivants, les arguments de la commande. Fichier compr.ml if sys__interactive then () else begin let erreur = ref false in if vect_length command_line >= 2 & command_line.(1) = "-d" then for i = 2 to vect_length command_line - 1 do try d´ecompresse_fichier command_line.(i) with Erreur -> erreur := true done else for i = 1 to vect_length command_line - 1 do try compresse_fichier command_line.(i) with Erreur -> erreur := true done; exit (if !erreur then 2 else 0) end;; Les deux boucles r´ecup`erent l’exception Erreur pour passer au prochain argument de la ligne de commande en cas d’erreur. On positionne cependant le drapeau erreur, pour pouvoir renvoyer au syst`eme d’exploitation un code d’erreur appropri´e : le code z´ero si aucune erreur ne s’est produite, un code non nul sinon. Il est maintenant temps de passer aux choses s´erieuses : l’impl´ementation des algorithmes de compression et de d´ecompression. 13.3 L’algorithme de Huffman L’algorithme de compression de Huffman repose sur l’observation que certains caract` eres apparaissent plus fr´equemment que d’autres dans les fichiers. Par exemple, dans un fichier de texte, e apparaˆıt plus souvent que z et l’espace apparaˆıt plus souvent que le caract`ere tilde. Au lieu de coder chaque caract`ere sur huit bits quelle que soit L’algorithme de Huffman 241 sa fr´equence, nous allons attribuer des codes de longueur variable aux caract`eres, en faisant en sorte que les caract`eres les plus fr´equents re¸coivent des codes courts (moins de huit bits) et les caract`eres les plus rares des codes longs (´eventuellement plus de huit bits). Par exemple, le codage suivant conviendrait pour du texte ´ecrit en fran¸cais : espace 110 t 1000 e 010 i 0010 s 1001 r 0001 a 0011 u 11101 n 0111 l 10111 pour les lettres les plus fr´equentes et ainsi de suite jusqu’aux lettres les plus rares : X 11100110111100111 Y 011010001010000010 Z 011010001010000001 Compression Compresser un fichier consiste `a remplacer chaque octet du fichier par la suite de bits qui l’encode, puis `a ´ecrire octet par octet la suite de bits obtenus. Avec le codage ci-dessus, le mot utile suivi d’un espace est transform´e en la suite de bits 11101.1000.0010.10111.010.110, c’est-`a-dire en les trois octets 55 (11101100), 168 (00010101) et 107 (11010110). (On a choisi arbitrairement de lire les nombres binaires avec le bit de poids faible `a gauche et le bit de poids fort `a droite.) Le texte compress´e occupe trois octets, au lieu de six pour le texte d’origine. Pour ´eviter les probl`emes qui se posent quand le dernier octet du fichier compress´e n’est pas enti`erement rempli, on convient de terminer tous les fichiers compress´es par un code sp´ecial de fin. Ce code n’apparaissant qu’une fois pour tout le fichier, il peut ˆetre choisi assez long. Voici maintenant la fonction qui compresse un fichier (correspondant au descripteur de fichier entr´ee) et ´ecrit le r´esultat sur un autre fichier (correspondant au descripteur de fichier sortie). Fichier huffman.ml type table_de_codage = { caract`ere: int list vect; mutable fin: int list };; let encode entr´ee sortie codage = esbit__initialise (); try while true do let c = input_char entr´ee in do_list (esbit__´ecrire_bit sortie) codage.caract`ere.(int_of_char c) done with End_of_file -> (* fin du fichier d’entr´ee *) do_list (esbit__´ecrire_bit sortie) codage.fin; esbit__finir sortie;; 242 Compression de fichiers Le codage employ´e est repr´esent´e par un enregistrement du type table_de_codage. La partie caract`ere est un tableau de 256 codes (un pour chaque octet). La partie fin est le code signalant la fin du fichier compress´e. Les codes sont repr´esent´es par des listes d’entiers, 0 ou 1. La lecture du fichier d’entr´ee se fait avec la fonction input_char de la biblioth`eque standard. Cette fonction renvoie le prochain caract`ere du canal d’entr´ee pass´e en argument. Elle d´eclenche l’exception End_of_file lorsque la fin du fichier est atteinte. La mani`ere habituelle de lire tous les caract`eres d’un fichier est de faire input_char `a l’int´erieur d’une boucle infinie while true do . . . done. L’exception End_of_file de fin de fichier fait sortir de la boucle ; elle doit ˆetre r´ecup´er´ee par une construction try . . . with. La fonction encode fait appel `a un module esbit (pour « entr´ees-sorties bit `a bit »), qui permet d’´ecrire sur un fichier non pas octet par octet comme les fonctions d’entr´ee-sortie usuelles, mais bit par bit. Nous impl´ementerons ce module plus tard. Pour l’instant, voici son l’interface : Fichier esbit.mli value initialise : unit -> unit and ´ecrire_bit : out_channel -> int -> unit and lire_bit : in_channel -> int and finir : out_channel -> unit;; Les fonctions ´ecrire_bit et lire_bit permettent d’´ecrire ou de lire un bit, repr´esent´e par les entiers 0 ou 1. La fonction initialise doit ˆetre appel´ee avant le premier appel `a ´ecrire_bit ou lire_bit. La fonction finir doit ˆetre appel´ee apr`es le dernier appel `a ´ecrire_bit, pour effectuer les ´eventuelles ´ecritures en attente. La fonction encode montre un bel exemple d’application partielle : on it`ere, avec la fonctionnelle do_list, la fonction (esbit__´ecrire_bit sortie), obtenue par application partielle de esbit__´ecrire_bit `a la sortie courante ; cette fonction n’est donc calcul´ee qu’une fois, avant de lancer l’it´eration. D´ecompression La d´ecompression se heurte `a un petit probl`eme : dans la suite de bits produite par l’algorithme de compression, rien ne marque les s´eparations entre les codes des diff´erents octets. Il est cependant possible de reconstituer le texte d’origine, `a condition que le codage employ´e ne soit pas ambigu : aucun code d’un caract`ere ne doit ˆetre pr´efixe du code d’un autre caract`ere. Supposons par exemple que le caract`ere o a pour code 0101, qui a pour pr´efixe 010, le code de e. Alors la suite de bits 01010111 repr´esente aussi bien el que on. Au contraire, si aucun code n’est pr´efixe d’un autre, il y a une et une seule mani`ere de d´ecouper la suite de bits contenue dans le fichier compress´e. Pour d´ecrire plus pr´ecis´ement le processus de d´ecodage, il est commode de repr´esenter le codage sous la forme d’un arbre de Huffman. C’est est un arbre binaire dont les feuilles sont des caract`eres. Tout codage non ambigu est repr´esent´e par un arbre de Huffman, de la mani`ere suivante : le code de chaque caract`ere est le chemin qui m`ene de la racine de l’arbre `a la feuille portant ce caract`ere, avec la convention que 0 signifie « prendre la branche de gauche » et 1 signifie « prendre la branche de L’algorithme de Huffman 243 droite ». Par exemple, voici l’arbre de Huffman pour le codage donn´e page 241 (nous repr´esentons les arbres avec la racine en haut et les feuilles en bas, comme c’est l’usage en informatique) : 1 1 0 espace 0 1 0 e 0 0 1 s 0 1 1 a 1 1 n 0 t 0 i 0 1 r 1 0 1 u 1 1 1 l La d´ecompression est tr`es simple quand on dispose de l’arbre de Huffman du codage. On part de la racine de l’arbre. Si on est sur un noeud, on lit le prochain bit du fichier compress´e et on va `a gauche si c’est z´ero et `a droite si c’est un. Quand on aboutit sur une feuille, on ´emet la lettre correspondante et on repart de la racine de l’arbre. Cet algorithme s’´ecrit sans difficult´es en Caml. (On a introduit un second type de feuille, le constructeur Fin, pour repr´esenter le code de fin de fichier.) Fichier huffman.ml type arbre_de_huffman = | Lettre of char | Fin | Noeud of arbre_de_huffman * arbre_de_huffman;; let d´ecode entr´ee sortie arbre = esbit__initialise (); let rec parcours = function | Fin -> () | Lettre c -> output_char sortie c; parcours arbre | Noeud(gauche, droite) -> if esbit__lire_bit entr´ee = 0 then parcours gauche else parcours droite in parcours arbre;; D´etermination d’un codage adapt´e On peut utiliser les fonctions encode et d´ecode avec un codage de Huffman fix´e, d´etermin´e une fois pour toutes `a partir des fr´equences moyennes d’apparition des caract` eres dans les textes fran¸cais (par exemple). Cependant, la compression risque d’ˆetre peu efficace sur d’autres types de textes (des programmes Caml, par exemple) ou sur des fichiers contenant autre chose que du texte (des images num´eris´ees, par exemple). Pour plus de g´en´eralit´e, il vaut mieux d´eterminer les fr´equences des caract`eres dans 244 Compression de fichiers le fichier `a compresser, puis construire un codage de Huffman adapt´e `a cette distribution de fr´equence particuli`ere. Bien entendu, le d´ecompresseur ne peut pas deviner le codage que le compresseur a utilis´e ; le compresseur ´ecrit donc ce codage en tˆete du fichier compress´e. Calculer les fr´equences d’apparition (c’est-`a-dire le nombre d’occurrences) des caract` eres dans un fichier ne pr´esente aucune difficult´e. Fichier huffman.ml let fr´equences entr´ee = let fr = make_vect 256 0 in begin try while true do let c = int_of_char(input_char entr´ee) in fr.(c) <- fr.(c) + 1 done with End_of_file -> () end; fr;; La fonction ci-dessus renvoie un tableau de 256 entiers qui donne le nombre d’occurrences de chaque caract`ere dans le fichier entr´ee. D´eterminer un codage adapt´e `a ce tableau de fr´equences est plus difficile. Voici un algorithme qui construit un arbre de Huffman petit `a petit, `a partir d’un ensemble de feuilles, une par caract`ere apparaissant dans le fichier, plus une pour la fin du fichier. Chaque feuille est annot´ee par la fr´equence d’apparition du caract`ere correspondant : 40 a 60 e 30 i 25 o 20 u 10 y 1 fin On choisit deux feuilles ayant la plus petite fr´equence et on les regroupe en un arbre : 40 a 60 e 30 i 25 o 20 u 11 0 y 1 fin L’arbre construit re¸coit comme fr´equence la somme des fr´equences des deux lettres qu’il regroupe. On r´ep`ete ensuite ce proc´ed´e, regroupant `a nouveau les deux arbres ayant la plus petite fr´equence, jusqu’`a obtenir finalement un arbre unique. Dans notre exemple, le regroupement des arbres de fr´equences 11 et 20 donne : 40 a 60 e 30 i 25 o 31 0 u 1 0 y 1 fin On regroupe ensuite les arbres de fr´equences 30 et 25, puis 31 et 40, puis 55 et 60. En regroupant les deux arbres restants, on obtient enfin l’arbre de Huffman recherch´e : L’algorithme de Huffman 245 0 0 e 1 0 i 1 o 1 0 a 1 0 u 1 0 y 1 fin 186 Traduisons maintenant cet algorithme en Caml. Fichier huffman.ml let construire_arbre fr´equences = let prio = ref (fileprio__ajoute fileprio__vide 1 Fin) in let nombre_d’arbres = ref 1 in for c = 0 to 255 do if fr´equences.(c) > 0 then begin prio := fileprio__ajoute !prio fr´equences.(c) (Lettre(char_of_int c)); incr nombre_d’arbres end done; for n = !nombre_d’arbres downto 2 do let (fr´eq1, arbre1, prio1) = fileprio__extraire !prio in let (fr´eq2, arbre2, prio2) = fileprio__extraire prio1 in prio := fileprio__ajoute prio2 (fr´eq1 + fr´eq2) (Noeud(arbre1, arbre2)) done; let (_, arbre, _) = fileprio__extraire !prio in arbre;; Pour g´erer l’ensemble d’arbres et les fr´equences associ´ees, on a utilis´e les fonctions d’un module fileprio impl´ementant la structure de donn´ees connue sous le nom de file d’attente avec priorit´e. Voici l’interface de ce module : Fichier fileprio.mli type ’a t;; value vide: ’a t and ajoute: ’a t -> int -> ’a -> ’a t and extraire: ’a t -> int * ’a * ’a t;; exception File_vide;; Les deux op´erations de base sur une file d’attente avec priorit´e sont l’ajout d’un ´el´ement dans la file, avec une certaine priorit´e (un entier), et l’extraction d’un ´el´ement ayant la priorit´e la plus faible. L’op´eration d’extraction renvoie, en plus de l’´el´ement extrait, la priorit´e de cet ´el´ement et la file d’attente priv´ee de cet ´el´ement. Elle d´eclenche l’exception File_vide si on l’applique `a la file vide. On donne en annexe de ce chapitre (section 13.4) deux impl´ementations possibles du module fileprio. `Apartir de l’arbre de Huffman renvoy´e par la fonction construire_arbre, on calcule une table de codage adapt´ee `a la fonction encode, comme suit. 246 Compression de fichiers Fichier huffman.ml let arbre_vers_codage arbre = let codage = { caract`ere = make_vect 256 []; fin = [] } in let rec remplir_codage pr´efixe = function | Lettre c -> codage.caract`ere.(int_of_char c) <- rev pr´efixe | Fin -> codage.fin <- rev pr´efixe | Noeud(arbre1, arbre2) -> remplir_codage (0 :: pr´efixe) arbre1; remplir_codage (1 :: pr´efixe) arbre2 in remplir_codage [] arbre; codage;; La fonction locale remplir_codage effectue une exploration exhaustive de l’arbre. Son argument pr´efixe contient le chemin (invers´e) de la racine jusqu’au noeud courant. Lorsqu’on atteint une feuille, on remet le chemin `a l’endroit et on le stocke dans la case correspondante du codage. Compression et d´ecompression de fichiers Il est temps de recoller tous les morceaux pour d´efinir les fonctions de compression et de d´ecompression d’un fichier. Le dernier probl`eme `a r´esoudre est celui du stockage du codage utilis´e `a la compression en tˆete du fichier compress´e. Pour rester simple, nous stockons ce codage sous la forme de son arbre de Huffman, en utilisant les fonctions d’entr´ee-sortie structur´ee output_value et input_value. La fonction pr´ed´efinie output_value, de type out_channel -> ’a -> unit, ´ecrit une repr´esentation de son argument sous forme d’une suite d’octets sur le canal sp´ecifi´e. L’argument est une structure Caml quelconque (ou presque : elle ne doit pas contenir de valeurs fonctionnelles). La fonction input_value effectue l’op´eration inverse : elle lit une suite d’octets sur un canal et renvoie la structure Caml correspondante. Ces deux fonctions sont tr`es rapides et ´evitent d’avoir `a ´ecrire soi-mˆeme des fonctions de conversion entre structures de donn´ees et suites d’octets. Le format de donn´ees utilis´e pour stocker l’objet dans le fichier n’est pas du texte directement lisible par l’utilisateur, mais un codage binaire de l’objet. Il est destin´e `a ˆetre relu par des machines, non par des humains. Un fichier compress´e se compose donc d’un arbre de Huffman ´ecrit par output_value, suivi par les donn´ees compress´ees ´ecrites par encode. Fichier huffman.ml let compresse entr´ee sortie = let fr´eq = fr´equences entr´ee in let arbre = construire_arbre fr´eq in let codage = arbre_vers_codage arbre in output_value sortie arbre; seek_in entr´ee 0; encode entr´ee sortie codage;; La fonction pr´ed´efinie seek_in positionne le pointeur de lecture d’un canal d’entr´ee. Le pointeur de lecture du canal entr´ee, qui est `a la fin du fichier au retour de la fonction Annexes 247 fr´equences, est donc remis au d´ebut du fichier par l’appel seek_in entr´ee 0. Cela permet `a encode de relire les octets du fichier `a compresser. Fichier huffman.ml let d´ecompresse entr´ee sortie = let arbre = input_value entr´ee in d´ecode entr´ee sortie arbre;; 13.4 Annexes Pour finir le programme de compression, il reste `a impl´ementer les files d’attente avec priorit´e (module fileprio) et les entr´ees-sorties bit `a bit (module esbit). Files d’attente avec priorit´e On rappelle l’interface du module fileprio : Fichier fileprio.mli type ’a t;; value vide: ’a t and ajoute: ’a t -> int -> ’a -> ’a t and extraire: ’a t -> int * ’a * ’a t;; exception File_vide;; Une premi`ere repr´esentation des files d’attente consiste en des listes de paires (priorit ´e, ´el´ement), tri´ees par ordre de priorit´e croissante. De la sorte, l’´el´ement de priorit´e minimale est toujours en tˆete de la liste et la fonction extraire est tr`es simple. Fichier fileprio.ml type ’a t == (int * ’a) list;; let vide = [];; let extraire = function | [] -> raise File_vide | (prio, elt) :: reste -> (prio, elt, reste);; La fonction ajoute est un peu plus compliqu´ee : il faut parcourir la liste jusqu’`a trouver un ´el´ement de priorit´e plus grande que l’´el´ement `a ins´erer. Fichier fileprio.ml let rec ajoute file prio elt = match file with | [] -> [(prio, elt)] | (prio1, elt1) :: reste -> if prio < prio1 then (prio, elt) :: reste else (prio1, elt1) :: ajoute reste prio elt;; Avec cette impl´ementation des files d’attente, l’op´eration extraire est en temps constant, mais l’op´eration ajoute est en temps n/2 en moyenne et n dans le pire des cas, o`u n est le nombre d’´el´ements de la file d’attente. Dans l’algorithme de Huffman, on 248 Compression de fichiers ins`ere jusqu’`a 256 ´el´ements dans des files de 0 `a 255 ´el´ements, ce qui donne, au pire, `a peu pr`es 32000 tours dans la fonction ajoute. Il est `a craindre que cette impl´ementation na¨ıve ne soit pas assez efficace. Voici une seconde impl´ementation du module fileprio, o`u les files sont repr´esent´ees par des arbres tournoi (aussi appel´es heaps, « tas » dans la litt´erature en anglais). Fichier fileprio.ml type ’a t = | Vide | File of int * ’a * ’a t * ’a t;; let vide = Vide;; Les feuilles de l’arbre (constructeur Vide) ne portent pas d’information. Les noeuds de l’arbre (constructeur File) portent chacun un ´el´ement de la file, avec sa priorit´e, plus deux sous-arbres, traditionnellement appel´es « le fils gauche » et « le fils droit ». On impose la condition suivante : La priorit´e d’un noeud est inf´erieure ou ´egale `a la priorit´e de tous les noeuds contenus dans ses fils gauche et droit. Ainsi, les priorit´es vont en croissant quand on se d´eplace de la racine vers une feuille. Voici un exemple d’arbre binaire croissant : 2, a 5, b 3, c 8, f 4, d 9, e Comme dans le cas de l’impl´ementation par des listes ordonn´ees, l’´el´ement de la file ayant la plus faible priorit´e est facile `a trouver : c’est toujours l’´el´ement `a la racine de l’arbre. Pour impl´ementer extraire, il reste donc `a savoir combiner les deux fils de la racine en un seul arbre binaire croissant, qui repr´esente la file de d´epart priv´ee de son ´el´ement le moins prioritaire. La racine du nouvel arbre est l’´el´ement de plus petite priorit´e parmi ceux qui restent. Ce ne peut ˆetre que le sommet du fils gauche ou le sommet du fils droit, puisque tous les autres noeuds sont moins prioritaires. On d´eplace donc celui des deux sommets qui a la plus petite priorit´e, pour le mettre `a la racine. vide 5, b 3, c 8, f 4, d 9, e 3, c 5, b vide 8, f 4, d 9, e Bien entendu, il y a maintenant un trou dans l’un des deux fils, trou qu’il faut `a son tour combler en r´ep´etant le mˆeme raisonnement. Annexes 249 3, c 5, b vide 8, f 4, d 9, e 3, c 5, b 4, d 8, f 9, e La proc´edure s’arrˆete lorsqu’elle atteint une feuille de l’arbre. Le r´esultat est bien un arbre binaire croissant contenant les mˆemes ´el´ements que celui de d´epart, moins la racine. Fichier fileprio.ml let rec enl`eve_sommet = function | Vide -> raise File_vide | File(prio, elt, Vide, Vide) -> Vide | File(prio, elt, gauche, Vide) -> gauche | File(prio, elt, Vide, droite) -> droite | File(prio, elt, (File(prio_g, elt_g, _, _) as gauche), (File(prio_d, elt_d, _, _) as droite)) -> if prio_g < prio_d then File(prio_g, elt_g, enl`eve_sommet gauche, droite) else File(prio_d, elt_d, gauche, enl`eve_sommet droite);; let extraire = function | Vide -> raise File_vide | File(prio, elt, _, _) as file -> (prio, elt, enl`eve_sommet file);; L’ajout d’un ´el´ement `a un arbre binaire croissant suit le mˆeme principe. Si le nouvel ´el´ement a une priorit´e plus haute que la racine, la fonction d’ajout s’appelle r´ecursivement pour l’ajouter au fils gauche ou au fils droit. Si le nouvel ´el´ement est moins prioritaire que la racine, elle place le nouvel ´el´ement `a la racine et s’appelle r´ecursivement pour ajouter l’ancienne racine dans un des deux fils. La fonction s’arrˆete lorsqu’elle arrive sur une feuille. Voici un exemple d’insertion de z avec la priorit´e 4, o`u l’on choisit d’aller une fois `a gauche, puis une fois `a droite. 3, c 5, b 4, d 8, f 9, e 3, c 4, z 4, d 8, f 5, b 9, e Dans la descente r´ecursive, le choix entre fils gauche et fils droit est arbitraire. Cependant, il ne faut pas choisir toujours le fils gauche ou toujours le fils droit : apr`es plusieurs ajouts, l’arbre obtenu serait tr`es d´es´equilibr´e, avec une longue branche vers la gauche ou vers la droite. Pour que l’extraction et l’ajout soient efficaces, il faut que l’arbre ait les branches les plus courtes possible, c’est-`a-dire qu’il soit aussi bien ´equilibr´e que 250 Compression de fichiers possible. Une mani`ere d’obtenir ce r´esultat est d’ins´erer toujours du mˆeme cˆot´e (disons, `a droite), mais de permuter les fils gauche et droit `a chaque descente r´ecursive. Cela suffit `a garantir que l’arbre est toujours ´equilibr´e, mˆeme apr`es une longue s´equence d’ajouts. Fichier fileprio.ml let rec ajoute file prio elt = match file with | Vide -> File(prio, elt, Vide, Vide) | File(prio1, elt1, gauche, droite) -> if prio <= prio1 then File(prio, elt, ajoute droite prio1 elt1, gauche) else File(prio1, elt1, ajoute droite prio elt, gauche);; Pour ce qui est de l’efficacit´e de cette impl´ementation, on constate qu’un appel de ajoute ou de extraire parcourt au plus une branche enti`ere de l’arbre. Comme l’arbre est ´equilibr´e, la longueur d’une branche est de l’ordre de log2 n, o`u n est le nombre d’´el´ements dans l’arbre. L’ajout et l’extraction se font donc en temps logarithmique en le nombre d’´el´ements de la file d’attente, ce qui donne une bien meilleure efficacit´e que l’impl´ementation `a base de listes. En particulier, la construction de l’arbre de Huffman n´ecessite moins de 2000 tours dans la fonction ajoute, soit huit fois moins qu’avec l’impl´ementation na¨ıve. Entr´ees-sorties par bits Nous passons maintenant aux entr´ees-sorties bit `a bit (module esbit). Fichier esbit.mli value initialise : unit -> unit and ´ecrire_bit : out_channel -> int -> unit and lire_bit : in_channel -> int and finir : out_channel -> unit;; Les entr´ees-sorties standard de Caml Light pr´esentent les fichiers comme une suite d’octets. Pour voir un fichier comme une suite de bits, nous allons traiter les octets comme des paquets de huit bits. Arbitrairement, on ordonne les bits des poids faibles vers les poids forts. Ainsi, l’entier 143, c’est-`a-dire 10001111 en binaire, est vu comme la suite de huit bits 1, 1, 1, 1, 0, 0, 0, 1. Pour transformer des op´erations bit `a bit en op´erations octet par octet, nous utilisons un tampon d’au plus huit bits contenant l’octet en cours d’´ecriture ou de lecture. Le tampon est repr´esent´e par un enregistrement `a deux champs mutables, le champ val qui contient l’octet en cours et le champ nbits qui indique le nombre de bits valides dans cet octet. Fichier esbit.ml type tampon = { mutable val: int; mutable nbits: int };; let tampon = { val = 0; nbits = 0 };; let initialise () = tampon.val <- 0; tampon.nbits <- 0;; Annexes 251 Voici par exemple la situation en cours d’´ecriture. (On a marqu´e en gris´e les bits qui ont ´et´e ´ecrits par la fonction ´ecrire_bit.) Fichier : 11111110 10101010 00000001 -------- -------- -------- Tampon : 11010000 tampon.val tampon.nbits Position courante d’´ecriture L’´ecriture d’un bit consiste simplement `a le stocker dans le bit num´ero nbits de val, puis `a incr´ementer nbits. Lorsque nbits atteint 8, on ´ecrit l’octet val sur le fichier et on repart avec nbits valant 0. Fichier esbit.ml let ´ecrire_bit sortie bit = tampon.val <- tampon.val lor (bit lsl tampon.nbits); tampon.nbits <- tampon.nbits + 1; if tampon.nbits >= 8 then begin output_char sortie (char_of_int tampon.val); tampon.val <- 0; tampon.nbits <- 0 end;; let finir sortie = if tampon.nbits > 0 then output_char sortie (char_of_int tampon.val);; Les op´erateurs infixes lor et lsl sont respectivement le « ou » bit `a bit entre entiers et le d´ecalage logique `a gauche. En particulier, bit lsl tampon.nbits est un entier avec le bit num´ero tampon.nbits ´egal `a bit et tous les autres bits nuls. La situation en cours de lecture est tr`es sym´etrique. La seule diff´erence est que nbits contient maintenant le nombre de bits restant `a lire dans val. (On a marqu´e en gris´e les bits qui ont ´et´e lus par la fonctions lire_bit.) Fichier : 11111110 10101010 00000001 11010011 00101111 11010101 Tampon : 10011000 tampon.val tampon.nbits Position courante de lecture Fichier esbit.ml let lire_bit entr´ee = if tampon.nbits <= 0 then begin tampon.val <- int_of_char(input_char entr´ee); tampon.nbits <- 8 end; let res = tampon.val land 1 in 252 Compression de fichiers tampon.val <- tampon.val lsr 1; tampon.nbits <- tampon.nbits - 1; res;; Les op´erateurs infixes land et lsr sont respectivement le « et » bit `a bit entre entiers et le d´ecalage logique `a droite. 13.5 Mise en pratique Tous les modules du programme sont maintenant ´ecrits ; il reste `a les compiler et `a les lier entre eux. $ camlc -c esbit.mli $ camlc -c esbit.ml $ camlc -c fileprio.mli $ camlc -c fileprio.ml $ camlc -c huffman.mli $ camlc -c huffman.ml $ camlc -c compr.ml $ camlc -o compr esbit.zo fileprio.zo huffman.zo compr.zo Pour essayer le programme : $ camlrun compr monfichier $ camlrun compr -d monfichier.cpr Une autre mani`ere de proc´eder est de compiler le programme `a partir du syst`eme interactif, avec les commandes compile et load_object, comme d´ecrit au chapitre 10, section 10.6. La compression s’effectue alors par compr__compresse_fichier "fich" et la d´ecompression par compr__d´ecompresse_fichier "fich.cpr". 13.6 Pour aller plus loin L’algorithme de Huffman employ´e ici n’est certainement pas le meilleur algorithme de compression existant `a l’heure actuelle. Outre un taux de compression pas tr`es ´elev´e, il pr´esente deux inconv´enients pratiques : le fichier compress´e doit contenir l’arbre de Huffman construit au codage et le fichier d’entr´ee doit ˆetre lu deux fois. Le premier trait est gˆenant pour les petits fichiers, pour lesquels la taille de l’arbre de Huffman n’est pas n´egligeable devant la taille du fichier produit ; en particulier, le fichier compress´e peut devenir plus gros que le fichier d’entr´ee. Le deuxi`eme trait implique que l’algorithme de Huffman n’est pas adapt´e `a la compression “au vol” de donn´ees, comme dans le cas des donn´ees qui transitent `a travers un modem et une ligne t´el´ephonique. Il existe une variante de l’algorithme de Huffman qui ne pr´esente pas ces deux inconv´enients : le codage de Huffman dynamique. L’id´ee est de changer d’arbre de Huffman en cours de compression. On part d’un arbre de Huffman ´equilibr´e, correspondant au cas o`u tous les caract`eres ont la mˆeme fr´equence. On lit les caract`eres sur l’entr´ee, en tenant `a jour les fr´equences des caract`eres d´ej`a lus. Chaque caract`ere est cod´e avec l’arbre de Huffman courant, puis l’arbre est modifi´e pour qu’il corresponde toujours aux fr´equences des caract`eres d´ej`a lus. Il n’est pas obligatoire de reconstruire Pour aller plus loin 253 l’arbre `a partir de z´ero `a chaque nouveau caract`ere : on arrive `a modifier l’arbre de mani`ere locale et incr´ementale. Non seulement une deuxi`eme passe sur l’entr´ee devient alors inutile, mais il n’est mˆeme plus n´ecessaire de transmettre l’arbre de Huffman au d´ecompresseur : la d´ecompression part du mˆeme arbre initial que la compression, et le modifie incr´ementalement `a chaque caract`ere d´ecod´e, en suivant exactement le mˆeme algorithme que le compresseur. De la sorte, l’arbre de d´ecodage et l’arbre de codage ´evoluent en phase. Sur des fichiers assez longs, l’algorithme de Huffman dynamique atteint les mˆemes taux de compression que l’algorithme de Huffman statique. Pour atteindre des taux de compression plus ´elev´es, il faut passer `a une autre famille d’algorithmes de compression, dus `a Lempel et Ziv, qui exploitent une autre source de redondance dans les fichiers de donn´ees : outre le fait que certains caract`eres apparaissent plus fr´equemment que d’autres, on trouve aussi des s´equences de plusieurs caract`eres qui apparaissent plus fr´equemment que d’autres. Par exemple, dans un programme Caml, les mots-cl´es comme let ou function reviennent tr`es souvent. L’id´ee des algorithmes de Lempel-Ziv est d’attribuer un code `a ces chaˆınes plus fr´equentes que les autres, et de les remplacer par leur code. Cette id´ee se prˆete `a de nombreuses variantes, qui diff`erent par la m´ethode de reconnaissance des chaˆınes fr´equentes et par la mani`ere de les coder. Les compresseurs les plus efficaces combinent ces algorithmes avec l’algorithme de Huffman dynamique, r´einjectant la sortie du premier dans l’entr´ee du second, pour tirer parti des deux types de redondance. Bibliographie Des livres entiers ont ´et´e consacr´es aux divers algorithmes de compression ; voir par exemple Data compression : methods and theory de James Storer (Computer Science Press) et Text compression de Bell, Witten et Cleart (Prentice Hall). Pour ce qui est des algorithmes sur les files d’attente, on se reportera `a l’un des classiques de l’algorithmique, comme par exemple le volume 3 de The art of computer programming, de Donald Knuth (Addison-Wesley). 14 Simulation d’un processeur O`u l’on apprend `a g´erer les RISC. eaucoup de programmes d’ordinateur servent `a simuler des processus physiques, l’ordinateur offrant sa puissance de calcul pour faire ´evoluer les nombreux param`etres du syst`eme. Nous ´ecrivons maintenant un programme de ce type : il simule le fonctionnement d’un processeur, ou unit´e centrale d’ordinateur. Le simulateur lit des programmes ´ecrits dans le langage d’assemblage de notre processeur imaginaire et les ex´ecute comme le ferait un processeur r´eel. Pour l’instant, nous ´ecrirons les programmes assembleur `a la main ; dans le chapitre 15, nous verrons comment produire automatiquement ces programmes par traduction `a partir d’un langage de haut niveau. Ce chapitre constitue une introduction au mod`ele de processeurs RISC (reduced instruction set computer, processeur `a jeu d’instruction r´eduit). Le lecteur est suppos ´e connaˆıtre dans ses grandes lignes le fonctionnement d’un processeur et avoir d´ej`a programm´e, ne serait-ce qu’une fois, dans un langage d’assemblage. 14.1 Le pico-processeur Cette section d´ecrit l’architecture et le langage du processeur que nous simulons. Nous l’appellerons le pico-processeur, car il est encore plus simple que la plupart des micro-processeurs actuels. Sous ses airs de simplicit´e, le pico-processeur reste n´eanmoins tr`es proche de certains processeurs RISC bien connus, tels que l’Alpha ou le MIPS. Architecture Vu du programmeur, le pico-processeur se compose d’un ensemble de 32 registres, d’une m´emoire de code dans laquelle est stock´e le programme `a ex´ecuter et d’une m´emoire de donn´ees dans laquelle le programme peut stocker des r´esultats interm´ediaires. Chaque registre contient un mot machine, que nous prenons de mˆeme taille que les entiers de Caml Light (type int), c’est-`a-dire 31 bits. La plupart des machines actuelles 256 Simulation d’un processeur Registres : 0 registre 1 registre 2 ... registre 31 M´emoire de donn´ees : donn´ee 0 donn´ee 1 donn´ee 2 ... Adresse 0 Adresse 4 Adresse 8 M´emoire de code : instruction 0 instruction 1 instruction 2 ... Adresse 0 Adresse 4 Adresse 8 Figure 14.1: Le mod`ele du programmeur de la pico-machine emploient des mots de 32 bits ; les mots de 31 bits sont irr´ealistes, mais simplifient consid´erablement la simulation. Les registres sont num´erot´es de 0 `a 31. Ils sont « interchangeables », en ceci que n’importe quel registre peut ˆetre argument ou r´esultat de n’importe quelle op´eration : il n’y a pas de registre sp´ecifique `a telle ou telle op´eration. Cependant, le registre 0 a une propri´et´e particuli`ere : il contient toujours l’entier z´ero. Autrement dit, ´ecrire dans ce registre n’a aucun effet : il garde sa valeur d’origine, c’est-`a-dire 0. On peut douter de l’utilit´e d’un tel registre contenant toujours la mˆeme information. Et pourtant, il sert beaucoup : il permet de r´egulariser et de simplifier le jeu d’instructions, comme nous le verrons dans les exemples. Tout comme le banc de registres, la m´emoire de donn´ees est elle aussi organis´ee en mots. Comme sur les machines 32 bits modernes, les adresses des cases m´emoire sont multiples de 4 : le premier mot est `a l’adresse z´ero, le deuxi`eme `a l’adresse 4, etc. Enfin, la m´emoire de code contient une instruction par case. Les adresses de code sont des entiers : tr`es r´ealistement nos instructions sont toutes cod´ees sur un mot machine ; leurs adresses sont donc aussi multiples de 4. Cependant, pour simplifier la simulation, nous les rangeons dans un tableau Caml et les repr´esentons non pas par un entier mais par une valeur de type somme. Ainsi, la premi`ere instruction est `a l’adresse z´ero, mais rang´ee dans la case num´ero 0 du tableau des instructions, la deuxi`eme instruction est `a l’adresse quatre, mais rang´ee dans la case num´ero 1 du tableau des instructions, etc. Cette division de la m´emoire en deux zones n’est pas courante dans les processeurs. En g´en´eral, la m´emoire est constitu´ee de mots ou d’octets o`u l’on range indiff´eremment des donn´ees ou des instructions. C’est encore une fois pour simplifier la simulation que nous avons divis´e la m´emoire en deux zones, pour faciliter le d´ecodage des instructions, que nous gardons sous forme symbolique. L’alternative aurait ´et´e de coder effectivement les instructions sous la forme de nombres entiers et de les d´ecoder au moment de leur ex´ecution. La proc´edure de codage et de d´ecodage n’est pas tr`es int´eressante et alourdirait inutilement notre pr´esentation. Jeu d’instructions Le jeu d’instructions de la pico-machine est r´esum´e dans la figure 14.2. Comme sur la plupart des processeurs RISC, les op´erations arithm´etiques et logiques ont un format Le pico-processeur 257 Notations : r nom de registre (r 0, r 1, . . . , r 31) o nom de registre ou constante enti`ere (12, -34, . . . ) a constante enti`ere Syntaxe Instruction Effet add(r1, o, r2) Addition enti`ere r2 re¸coit r1 + o sub(r1, o, r2) Soustraction enti`ere r2 re¸coit r1 − o mult(r1, o, r2) Multiplication enti`ere r2 re¸coit r1 ∗ o div(r1, o, r2) Quotient entier r2 re¸coit r1/o and(r1, o, r2) «Et » bit `a bit r2 re¸coit r1 « et » o or(r1, o, r2) «Ou » bit `a bit r2 re¸coit r1 « ou » o xor(r1, o, r2) «Ou exclusif » bit `a bit r2 re¸coit r1 « ou exclusif » o shl(r1, o, r2) D´ecalage arithm´etique logique `a gauche r2 re¸coit r1 d´ecal´e `a gauche de o bits shr(r1, o, r2) D´ecalage arithm´etique logique `a droite r2 re¸coit r1 d´ecal´e `a droite de o bits slt(r1, o, r2) Test « inf´erieur » r2 re¸coit 1 si r1 < o, 0 sinon sle(r1, o, r2) Test « inf´erieur ou ´egal » r2 re¸coit 1 si r1 ≤ o, 0 sinon seq(r1, o, r2) Test « ´egal » r2 re¸coit 1 si r1 = o, 0 sinon load(r1, o, r2) Lecture m´emoire r2 re¸coit le contenu de l’adresse r1 + o store(r1, o, r2) ´Ecriture m´emoire le contenu de r2 est ´ecrit `a l’adresse r1 + o jmp(o, r) Branchement saute `a l’adresse o et stocke l’adresse de l’instruction suivant le jmp dans r braz(r, a) Branchement si z´ero saute `a l’adresse a si r = 0 branz(r, a) Branchement si pas z´ero saute `a l’adresse a si r 6= 0 scall(n) Appel syst`eme n est le num´ero de l’appel stop Arrˆet de la machine fin du programme Figure 14.2: Le jeu d’instructions de la pico-machine simple et uniforme : le premier argument est un registre, le deuxi`eme argument est soit un registre soit une constante et le r´esultat est mis dans un registre. Les op´erations n’acc`edent jamais directement `a la m´emoire. Les transferts de donn´ees entre registres et m´emoire de donn´ees sont assur´es par les instructions load et store. L’adresse du mot m´emoire acc´ed´e est toujours de la forme r + o, o`u r est le contenu d’un registre et o soit le contenu d’un registre, soit une constante. En d’autres termes, le pico-processeur n’a que deux modes d’adressage : index´e avec d´eplacement constant et index´e avec d´eplacement calcul´e. Les instructions de branchement sont au nombre de trois. On a d’une part deux branchements conditionnels, braz et branz, qui testent si un registre est nul ou non nul et sautent, le cas ´ech´eant, `a une adresse donn´ee. On dispose aussi d’un branchement inconditionnel, jmp, `a une adresse constante ou calcul´ee (contenue dans un registre). De plus, jmp ´ecrit l’adresse de l’instruction qui le suit dans un registre. Ceci permet de 258 Simulation d’un processeur faire facilement des appels de sous-programmes (voir ci-dessous). Enfin, nous avons ajout´e deux instructions sp´eciales qui n’ont pas toujours d’´equivalent dans les processeurs r´eels, mais qui sont tr`es utiles dans le cadre d’un simulateur : stop, qui arrˆete l’ex´ecution du programme, et scall (pour system call, appel syst`eme), qui repr´esente l’appel `a un (hypoth´etique) syst`eme d’exploitation, en particulier pour faire des entr´ees-sorties. Nous utiliserons deux appels syst`eme : l’un qui affiche `a l’´ecran le nombre contenu dans le registre r 1, l’autre qui lit un nombre au clavier et le place dans r 1. Idiomes de programmation Le lecteur habitu´e aux architectures CISC (complex instruction set computer, processeur `a jeu d’instructions complexe), comme par exemple le Pentium d’Intel, a sans doute ´et´e surpris par la pauvret´e du jeu d’instructions du pico-processeur. En particulier, il semble manquer un certain nombre d’instructions indispensables : l’affectation (instruction move), certaines comparaisons (on a « inf´erieur », mais pas « sup´erieur »), l’appel de sous-programme, le retour de sous-programme, la gestion de la pile, . . . Nous allons voir que toutes ces fonctionnalit´es s’expriment facilement en une ou deux instructions de la pico-machine. Z´ero comme argument Beaucoup d’op´erations utiles s’obtiennent en fixant `a z´ero un des deux arguments d’une instruction, en prenant soit la constante 0, soit le registre r 0 comme argument. Voici quelques exemples : add r1, 0, r2 Copie r1 dans r2 (instruction move) add r 0, n, r2 Met la constante n dans r2 (instruction move) sub r 0, r1, r2 Met l’oppos´e de r1 dans r2 (instruction neg) braz r 0, a Saute `a l’adresse a load r1, 0, r2 Lit le mot `a l’adresse (calcul´ee) r1 load r 0, a, r2 Lit le mot `a l’adresse (constante) a Le registre z´ero comme r´esultat Parfois, le r´esultat d’une op´eration est inutile. La mani`ere standard de s’en d´ebarrasser sans modifier aucun registre est de mettre r 0 comme registre de destination. Par exemple, jmp a, r 0 se branche `a l’adresse a, sans mettre l’adresse de retour dans aucun registre. N´egation bool´eenne et inversion de tests En supposant les valeurs de v´erit´e repr´esent´ees par 0 pour « faux » et autre chose que 0 pour « vrai », l’instruction seq r1, r 0, r2 calcule la n´egation d’une valeur de v´erit´e : si r1 est « faux » (nul), r2 est mis `a « vrai » ; si r1 est « vrai » (non nul), r2 est mis `a « faux ». Exemple d’application : le test « strictement plus grand » entre un registre r1 et un registre ou une constante o, avec r´esultat dans r2, se calcule par les deux instructions sle r1, o, r2 seq r2, r 0, r2 La premi`ere instruction calcule la n´egation du r´esultat d´esir´e (la n´egation de r1 > o est r1 ≤ o) ; la deuxi`eme calcule la n´egation de cette n´egation, obtenant le r´esultat d´esir´e. Le pico-processeur 259 Sous-programmes L’instruction jmp sert `a la fois pour appeler un sous-programme et pour revenir d’un sous-programme dans le programme principal. L’id´ee est de passer au sous-programme son adresse de retour dans un registre particulier. Par convention, nous utiliserons toujours le registre r 31 pour passer l’adresse de retour et les registres r 1, r 2, . . . pour passer les arguments et les r´esultats entre un sous-programme et le programme qui l’appelle. (Ce n’est qu’une convention, dans le but de rendre les programmes plus lisibles ; rien dans l’architecture de la pico-machine n’impose ce choix de registres.) Avec cette convention, voici comment s’´ecrit la fonction «moyenne arithm´etique » : Instruction 100 add r 1, r 2, r 1 104 div r 1, 2, r 1 108 jmp r 31, r 0 Et voici un programme d’essai qui appelle cette fonction : Instruction 0 scall 0 (lecture d’un nombre au clavier) 4 add r 1, 0, r 2 8 scall 0 (lecture d’un nombre au clavier) 12 jmp 100, r 31 16 scall 1 (´ecriture d’un nombre `a l’´ecran) 20 stop L’appel syst`eme scall 0 lit un nombre au clavier et le place dans r 1. Les instructions 0, 4 et 8 lisent donc deux nombres et les placent dans les registres r 2 et r 1. L’instruction 12 se branche `a l’instruction 100 (le d´ebut du sous-programme), apr`es avoir plac´e l’adresse de l’instruction 16 dans le registre r 31. On ex´ecute alors le code de la fonction «moyenne arithm´etique » : les instructions 100 et 104 calculent la moyenne de r 1 et de r 2 et placent le r´esultat dans r 1, conform´ement aux conventions d’appel des fonctions ; l’instruction 108 se branche `a l’adresse contenue dans r 31, c’est-`a-dire `a l’instruction 16. On continue donc l’ex´ecution du programme principal : affichage du registre r 1 (le r´esultat) par l’appel syst`eme scall 1 et arrˆet du programme. Gestion d’une pile Les conventions d’appel introduites ci-dessus posent probl`eme lorsqu’un sous-programme doit en appeler un autre, ou se rappeler lui-mˆeme : si un sous-programme f appelle un sous-programme g avec les conventions standard, f va mettre dans r 31 une adresse de retour pour g, d´etruisant ainsi le contenu courant de r 31, qui est l’adresse de retour pour f. La solution est bien sˆur de sauvegarder l’adresse de retour pour f sur une pile. La pico-machine ne fournit pas d’instructions push et pop pour g´erer une pile ; mais ces deux op´erations se programment facilement. On convient d’utiliser le registre r 30 comme pointeur de pile. La pile commence tout en haut de la m´emoire et croˆıt vers le bas. Le registre r 30 pointe vers le dernier mot empil´e. Avec ces conventions, empiler un registre, disons r 31, se traduit par sub r 30, 4, r 30 store r 30, 0, r 31 L’instruction sub alloue de la place pour le registre `a empiler ; l’instruction store stocke sa valeur `a l’emplacement allou´e. R´eciproquement, d´epiler r 31 se traduit par 260 Simulation d’un processeur load r 30, 0, r 31 add r 30, 4, r 30 L’instruction load recharge la valeur du registre ; l’instruction add r´ecup`ere la place qui lui ´etait allou´ee sur la pile. Comme exemple d’utilisation de la pile, voici un sous-programme qui calcule la fonction factorielle de la mani`ere r´ecursive classique. Adr. Instruction Commentaire 100 branz r 1, 112 si l’argument n n’est pas nul, aller en 112 104 add r 0, 1, r 1 mettre la constante 1 dans r 1 108 jmp r 31, r 0 retour `a l’appelant 112 sub r 30, 8, r 30 r´eserver deux mots dans la pile 116 store r 30, 4, r 31 empiler r 31 (l’adresse de retour) 120 store r 30, 0, r 1 et empiler r 1 (n) 124 sub r 1, 1, r 1 appel r´ecursif sur n − 1 128 jump 100, r 31 au retour, r 1 contient (n − 1)! 132 load r 30, 0, r 2 d´epile n (mis dans r 2) 136 load r 30, 4, r 31 d´epile l’adresse de retour 140 add r 30, 8, r 30 r´ecup`ere la place en pile 144 mult r 1, r 2, r 1 calcule n × (n − 1)! dans r 1 148 jmp r 31, r 0 retour `a l’appelant 14.2 Le simulateur Nous passons maintenant `a l’impl´ementation en Caml d’un simulateur de la picomachine. Cette impl´ementation se compose de deux programmes : l’un, le simulateur proprement dit, prend une repr´esentation binaire du code `a ex´ecuter et l’ex´ecute ; l’autre, appel´e l’assembleur, produit la repr´esentation binaire ex´ecutable `a partir d’un texte de programme ´ecrit dans le langage de la pico-machine. Nous ´etudierons l’assembleur dans la section 14.3 ; pour l’instant, voyons le simulateur. Description du pico-processeur On commence par un module code qui d´efinit le jeu d’instructions de la machine sous forme d’un type concret Caml, ainsi que certaines caract´eristiques de la machine. Fichier code.mli type registre == int;; type op´erande = | Reg of registre | Imm of int;; type instruction = | Op of op´eration * registre * op´erande * registre | Jmp of op´erande * registre | Braz of registre * int | Branz of registre * int Le simulateur 261 | Scall of int | Stop and op´eration = | Load | Store | Add | Mult | Sub | Div | And | Or | Xor | Shl | Shr | Slt | Sle | Seq;; value nombre_de_registres: int and sp: int and ra: int and taille_du_mot: int;; Les instructions sont d´ecrites par un ensemble de types somme et une abr´eviation de type (type registre == int). Une abr´eviation de type n’a pas de contenu s´emantique : le type figurant `a gauche du symbole == peut ˆetre partout remplac´e par le type qui figure `a droite. Dans notre cas, le type registre peut ˆetre partout remplac´e par int. On utilise des abr´eviations de type dans le but de rendre le code plus clair. La simplicit´e du jeu d’instructions de la machine se refl`ete dans la simplicit´e du type op´eration : il n’y a que 14 op´erations de base. Dans les instructions, ces op´erations sont regroup´ees dans le constructeur Op, puisque leurs arguments ont exactement la mˆeme forme (un registre source, un op´erande source, un registre destination). Il y a cinq formats d’instructions puisque Braz et Branz ont les mˆemes arguments. L’impl´ementation du module code fixe le nombre de registres `a 32 et donne des noms aux registres 30 (sp, pour stack pointer, pointeur de pile) et 31 (ra, pour return address, adresse de retour). Fichier code.ml let nombre_de_registres = 32 and sp = 30 and ra = 31 and taille_du_mot = 4;; L’´etat du pico-processeur Le module simul impl´emente le simulateur du pico-processeur : il fournit la fonction ex´ecute qui ex´ecute tout un programme, c’est-`a-dire un tableau d’instructions, en initialisant d’abord la m´emoire `a une taille donn´ee. Fichier simul.mli #open "code";; exception Erreur of string * int;; value lire_m´emoire : int -> int;; value ´ecrire_m´emoire : int -> int -> unit;; value lire_registre : int -> int;; value ´ecrire_registre : int -> int -> unit;; value tableau_des_appels_syst`eme: (int -> int) vect;; 262 Simulation d’un processeur value ex´ecute: instruction vect -> int -> unit;; L’impl´ementation de simul commence par une description Caml de l’´etat courant de la pico-machine. Cet ´etat se compose de l’´etat des registres, de la m´emoire de code, de la m´emoire de donn´ees, et enfin de la valeur courante du pointeur de programme (pc, pour program counter), qui contient l’adresse de la prochaine instruction `a ex´ecuter. Cet ´etat est donc d´ecrit par un type enregistrement `a champs mutables, contenant un tableau pour les registres, le pointeur de code, un tableau pour les instructions `a ex´ecuter et un tableau pour la m´emoire des donn´ees. Fichier simul.ml #open "code";; type ´etat_du_processeur = { registres: int vect; mutable pc: int; mutable code: instruction vect; mutable m´emoire: int vect };; let pico = { registres = make_vect nombre_de_registres 0; pc = 0; code = [| |]; m´emoire = [| |] };; La pico-machine est cr´e´ee par la d´efinition de la variable pico. Ses zones code et m´emoire des donn´ees sont provisoirement initialis´ees vides (elles ne seront allou´ees qu’au lancement de la machine, en fonction de la taille du programme `a ex´ecuter et de la taille des donn´ees `a traiter). Ex´ecution d’une instruction Le simulateur fait ´evoluer l’´etat du processeur, contenu dans la variable pico, en simulant l’ex´ecution des instructions. Pour cela, le simulateur appelle la fonction cycle_d’horloge qui ex´ecute une instruction et fait ´evoluer l’´etat de la machine en cons´equence. L’ex´ecution d’un programme consiste `a r´ep´eter cycle_d’horloge jusqu’`a ce qu’on rencontre une instruction stop. La fonction cycle_d’horloge devra lire et ´ecrire la m´emoire et les registres. Nous d´efinissons tout d’abord une batterie de fonctions auxiliaires qui v´erifient que les op´erations demand´ees par le programme sont l´egales et acc`edent `a des ressources r´eelles de la machine. Fichier simul.ml let lire_registre reg = if reg < 0 || reg >= nombre_de_registres then raise (Erreur ("registre ill´egal", reg)); pico.registres.(reg);; let ´ecrire_registre reg valeur = if reg < 0 || reg >= nombre_de_registres then Le simulateur 263 raise (Erreur ("registre ill´egal", reg)); if reg <> 0 then pico.registres.(reg) <- valeur;; let lire_instruction adresse = let adr = adresse / taille_du_mot in if adr < 0 || adr >= vect_length pico.code then raise (Erreur ("sortie de la zone code", adr)); if adresse mod taille_du_mot <> 0 then raise (Erreur ("pc non align´e", adresse)); pico.code.(adr);; let lire_m´emoire adresse = let adr = adresse / taille_du_mot in if adr < 0 || adr >= vect_length pico.m´emoire then raise (Erreur ("lecture en dehors de la m´emoire", adresse)); if adresse mod taille_du_mot <> 0 then raise (Erreur ("lecture non align´ee", adresse)); pico.m´emoire.(adr);; let ´ecrire_m´emoire adresse valeur = let adr = adresse / taille_du_mot in if adr < 0 || adr >= vect_length pico.m´emoire then raise (Erreur ("´ecriture en dehors de la m´emoire", adresse)); if adresse mod taille_du_mot <> 0 then raise (Erreur ("´ecriture non align´ee", adresse)); pico.m´emoire.(adr) <- valeur;; let valeur_op´erande = function | Reg r -> lire_registre r | Imm n -> n;; Dans le cas des acc`es `a la m´emoire, il faut tenir compte du fait que l’adresse d’un mot est toujours multiple de 4 ; il faut donc la diviser par 4 pour obtenir l’indice qui lui correspond dans le tableau pico.m´emoire. Fichier simul.ml let tableau_des_appels_syst`eme = make_vect 10 ((function x -> x) : int -> int);; let ex´ecute_appel_syst`eme appel argument = if appel < 0 || appel >= vect_length tableau_des_appels_syst`eme then raise(Erreur("mauvais appel syst`eme", appel)) else tableau_des_appels_syst`eme.(appel) argument;; La fonction cycle_d’horloge ex´ecute une instruction `a la fois : elle d´ecode et ex´ecute l’instruction actuellement point´ee par le compteur ordinal (PC), puis incr´emente ce compteur pour passer `a l’instruction suivante. Dans cette fonction, les op´erations de la machine sont simul´ees `a l’aide de fonctions Caml : +, -, *, et ainsi de suite pour les op´erations arithm´etiques et logiques ; lire_m´emoire et ´ecrire_m´emoire pour Load et Store. L’arrˆet du processeur, lorsqu’on rencontre l’instruction Stop, est mod´elis´e par un d´eclenchement d’exception (l’exception Arr^et). 264 Simulation d’un processeur Fichier simul.ml exception Arr^et;; let cycle_d’horloge () = let instruction = lire_instruction pico.pc in pico.pc <- pico.pc + taille_du_mot; match instruction with | Op(op´eration, reg1, op´erande, reg2) -> let arg1 = lire_registre reg1 and arg2 = valeur_op´erande op´erande in begin match op´eration with | Load -> ´ecrire_registre reg2 (lire_m´emoire (arg1 + arg2)) | Store -> ´ecrire_m´emoire (arg1 + arg2) (lire_registre reg2) | Add -> ´ecrire_registre reg2 (arg1 + arg2) | Mult -> ´ecrire_registre reg2 (arg1 * arg2) | Sub -> ´ecrire_registre reg2 (arg1 - arg2) | Div -> if arg2 = 0 then raise (Erreur("division par z´ero", pico.pc-1)) else ´ecrire_registre reg2 (arg1 / arg2) | And -> ´ecrire_registre reg2 (arg1 land arg2) | Or -> ´ecrire_registre reg2 (arg1 lor arg2) | Xor -> ´ecrire_registre reg2 (arg1 lxor arg2) | Shl -> ´ecrire_registre reg2 (arg1 lsl arg2) | Shr -> ´ecrire_registre reg2 (arg1 asr arg2) | Slt -> ´ecrire_registre reg2 (if arg1 < arg2 then 1 else 0) | Sle -> ´ecrire_registre reg2 (if arg1 <= arg2 then 1 else 0) | Seq -> ´ecrire_registre reg2 (if arg1 = arg2 then 1 else 0) end | Jmp(op´erande, reg) -> ´ecrire_registre reg pico.pc; pico.pc <- valeur_op´erande op´erande | Braz(reg, adresse) -> if lire_registre reg = 0 then pico.pc <- adresse | Branz(reg, adresse) -> if lire_registre reg <> 0 then pico.pc <- adresse | Scall(appel_syst`eme) -> ´ecrire_registre 1 (ex´ecute_appel_syst`eme appel_syst`eme (lire_registre 1)) | Stop -> raise Arr^et;; Ex´ecution d’un programme complet L’ex´ecution d’un programme complet (fonction ex´ecute) consiste `a charger la m´emoire avec le programme, `a allouer la m´emoire des donn´ees, `a initialiser le pointeur de pile et le compteur ordinal, puis `a lancer une boucle sans fin de cycles d’horloge, qui ne s’arrˆete qu’`a la rencontre de l’exception Arr^et. Enfin, puisque nous avons choisi par convention que le pointeur de pile serait le registre 30 et que la pile croˆıtrait vers le bas de la m´emoire, le registre sp est initialis´e `a la plus grande adresse m´emoire possible (plus un) au d´emarrage de la machine. Le simulateur 265 Fichier simul.ml let ex´ecute programme taille_m´emoire_en_octets = let taille_m´emoire_en_mots = (taille_m´emoire_en_octets + 3) / 4 in pico.code <- programme; pico.m´emoire <- make_vect taille_m´emoire_en_mots 0; pico.registres.(0) <- 0; pico.registres.(sp) <- taille_m´emoire_en_mots * taille_du_mot; pico.pc <- 0; try while true do cycle_d’horloge () done with Arr^et -> ();; Les appels syst`eme Il nous reste `a mettre en place un « pico-syst`eme d’exploitation », `a savoir les appels syst`eme pour la lecture et l’´ecriture d’un nombre. Nous d´efinissons donc les deux fonctions correspondantes et les rangeons dans le tableau des appels syst`eme. Fichier simul.ml let appel_syst`eme_read _ = try read_int () with Failure _ -> raise (Erreur ("erreur de lecture", 1)) and appel_syst`eme_write argument = print_int argument; print_newline (); argument;; tableau_des_appels_syst`eme.(0) <- appel_syst`eme_read; tableau_des_appels_syst`eme.(1) <- appel_syst`eme_write;; Dans un vrai processeur les appels syst`eme sont bien sˆur ´ecrits en assembleur et acc`edent directement aux ressources mat´erielles de la machine. Par exemple, la primitive d’impression irait (plus ou moins directement) ´ecrire dans la m´emoire d’´ecran de la machine chacun des chiffres du nombre, tandis que la routine de lecture d’un nombre interrogerait le clavier et transformerait les codes des touches frapp´ees en un nombre entier `a l’aide d’une boucle similaire `a celles utilis´ees dans nos analyseurs syntaxiques. Pour simplifier, c’est Caml qui nous permet cet acc`es direct aux ressources de la machine, ce qui nous ´evite de d´ecrire la connexion du processeur avec ses p´eriph´eriques. Le programme principal Pour lancer la machine sur un fichier de code pr´ealablement assembl´e, on se contente de lire ce code en m´emoire, puis de lancer la fonction ex´ecute. Pour simplifier la lecture du code machine, nous allons utiliser les fonctions d’entr´ee-sortie structur´ee que fournit le syst`eme Caml Light. La fonction pr´ed´efinie output_value, qui a pour type out_channel -> ’a -> unit, ´ecrit une repr´esentation de son argument (une valeur Caml quelconque) sous forme d’une suite d’octets sur le canal sp´ecifi´e. La fonction input_value effectue l’op´eration inverse : elle lit une suite d’octets sur un canal et renvoie la structure Caml correspondante. (Ces deux fonctions nous ont d´ej`a servi pour sauvegarder des arbres de Huffman `a la section 13.3.) 266 Simulation d’un processeur Le point d’entr´ee de la commande analyse les arguments fournis par l’utilisateur pour d´etecter la pr´esence d’un entier fixant la taille m´emoire `a utiliser ; sinon on lance la machine avec une taille de 1K mots. On surveille aussi les exceptions qui pourraient se produire pour afficher un message et rendre un code d’erreur ad´equat. Fichier exec.ml #open "code";; #open "simul";; exception Fichier_incorrect;; let ex´ecute_fichier nom_fichier taille_m´emoire = let canal = open_in_bin nom_fichier in let programme = try (input_value canal : instruction vect) with Failure _ -> raise Fichier_incorrect in close_in canal; ex´ecute programme taille_m´emoire;; exception Mauvais_arguments;; if sys__interactive then () else try if vect_length sys__command_line < 2 then raise Mauvais_arguments; let taille_m´emoire = if vect_length sys__command_line < 3 then 1024 else try int_of_string sys__command_line.(2) with Failure _ -> raise Mauvais_arguments in ex´ecute_fichier sys__command_line.(1) (taille_du_mot * taille_m´emoire); exit 0 with Mauvais_arguments -> prerr_endline "Usage: pico_run [taille m´emoire]"; exit 2 | Fichier_incorrect -> prerr_endline "Le fichier ne contient pas du code ex´ecutable"; exit 2 | Erreur(message, param) -> prerr_string "Erreur `a l’ex´ecution: "; prerr_string message; prerr_string " ("; prerr_int param; prerr_endline ")"; exit 2 | sys__Sys_error message -> prerr_string "Erreur du syst`eme: "; prerr_endline message; exit 2;; Le programme pico_run s’obtient par une s´erie d’appels au compilateur ind´ependant, suivie d’un appel `a l’´editeur de liens de Caml Light qui produit la commande elle-mˆeme. $ camlc -c code.mli L’assembleur 267 $ camlc -c code.ml $ camlc -c simul.mli $ camlc -c simul.ml $ camlc -c exec.ml $ camlc -o pico_run code.zo simul.zo exec.zo 14.3 L’assembleur Nous passons maintenant au programme qui se charge de transformer un texte ´ecrit dans le langage du processeur en une suite d’instructions directement compr´ehensibles par le processeur. Ce programme est traditionnellement appel´e l’assembleur. Il produit une suite d’instructions directement ex´ecutables, qu’il ´ecrit dans un fichier de r´esultat. Ce fichier est ensuite lu et charg´e en m´emoire programme `a la demande de l’utilisateur, par un programme sp´ecial qui lance l’ex´ecution. En g´en´eral ce « lanceur » est le syst`eme d’exploitation de l’ordinateur ; dans notre cas, c’est le programme pico_run de la section pr´ec´edente. Le langage de l’assembleur Le langage d’entr´ee de l’assembleur s’appelle en termes pr´ecis le langage d’assemblage et par abus l’assembleur. Le mot « assembleur » a donc deux sens en informatique : il d´esigne tantˆot un langage, tantˆot un programme de traduction. Pour ´eviter toute confusion, nous emploierons « langage d’assemblage » pour le langage d’instructions symboliques et « assembleur » pour le programme. Le principal travail de l’assembleur est de lire une repr´esentation textuelle des instructions de la machine et de la transformer en code ex´ecutable. Dans un processeur r´eel, le code ex´ecutable est une suite d’octets ou de mots qui encodent les instructions et leurs op´erandes sous forme binaire. Pour la pico-machine, la phase de transformation du programme source en suite de nombres sera remplac´ee par la traduction en syntaxe abstraite des instructions de la pico-machine. Par exemple on ´ecrira store sp, 1, r 1 dans le langage d’assemblage et l’assembleur de la pico-machine produira l’instruction Op (Store, 30, Imm 1, 1). Comme pour un processeur r´eel, l’assembleur effectue un codage des op´erations ; au contraire d’un processeur r´eel, le code n’est pas sous forme binaire. Il aurait ´et´e possible de coder r´eellement en nombres entiers, au prix d’inutiles complications. Un autre service que rend l’assembleur est de lib´erer le programmeur de la gestion des num´eros d’instructions. On ´ecrit les instructions `a la suite et l’assembleur les range automatiquement par num´eros croissants. Bien plus, il fournit la possibilit´e de rep´erer des instructions par des noms ; ce sont les ´etiquettes symboliques, qui font r´ef´erence `a des adresses d’instructions, sans que le programmeur ait `a calculer l’adresse absolue de l’instruction correspondante dans la zone code. Ce remplacement automatique d’adresses symboliques par des adresses absolues s’appelle la r´esolution des ´etiquettes. Le langage d’assemblage, tel qu’il est d´efini par l’assembleur, est donc plus riche et plus expressif que le langage ex´ecut´e par la machine, puisqu’il comporte ces ´etiquettes symboliques. L’assembleur fournit ´egalement un certain nombre d’abr´eviations ; par exemple, sp est un nom de registre l´egal de l’assembleur, qu’il transforme automatique268 Simulation d’un processeur ment en une r´ef´erence au registre num´ero 30. Il s’agit l`a encore de noms symboliques, automatiquement g´er´es par l’assembleur. Enfin, il enrichit et rend plus uniforme le jeu d’instructions de la machine, en ajoutant des pseudo-instructions qui se pr´esentent exactement comme des instructions de la machine mais sont en fait expans´ees en une ou plusieurs « vraies » instructions. Par exemple, notre assembleur sait coder les comparaisons « sup´erieur » et « sup´erieur ou ´egal » (instructions sgt et sge), qui sont initialement absentes du jeu d’instructions. Voici par exemple un fichier d’assembleur de la pico-machine, ´ecrit `a la main et qui programme la fonction factorielle. Fichier fact.asm # Le programme principal read # lecture de l’argument (dans r 1) jmp fact, ra # calcul de la factorielle write # ´ecriture du r´esultat (r 1) stop # La fonction factorielle(N) # L’argument N est dans r 1. Le r´esultat est mis dans r 1. fact: braz r 1, fact_0 # N = 0 ? sub sp, 8, sp # r´eserve deux places dans la pile store sp, 0, ra # sauvegarde de l’adresse de retour store sp, 4, r 1 # et de la valeur de N sub r 1, 1, r 1 jmp fact, ra # appel r´ecursif sur N-1 load sp, 4, r 2 # r´ecup´eration de la valeur de N mult r 1, r 2, r 1 # calcul de N * fact(N-1) load sp, 0, ra # r´ecup´eration de l’adresse de retour add sp, 8, sp # et de la place en pile jmp ra, r 0 # retour `a l’appelant fact_0: add r 0, 1, r 1 # mettre 1 dans r1 jmp ra, r 0 # retour `a l’appelant On a ´ecrit simplement jmp fact, ra en utilisant l’´etiquette symbolique d´efinie par fact: au lieu de jmp 16, r 31 qui ferait r´ef´erence au num´ero absolu de l’instruction braz r 1, fact_0 et au num´ero du registre d´edi´e `a l’adresse de retour. En observant le code assembleur de la fonction factorielle, on mesure la difficult´e qu’il y a `a ´ecrire un programme en assembleur plutˆot qu’en Caml. On comprend aussi bien mieux la diff´erence entre style imp´eratif et style fonctionnel : l’assembleur est par essence imp´eratif, car on ne travaille que par modification de registres. Vous constatez aussi qu’on doit tout g´erer soi-mˆeme, « `a la main », par exemple la r´ecursivit´e dans le cas de fact. En revanche, en assembleur toutes les ressources de la machine sont disponibles : on peut tout faire ; malheureusement, la contrepartie est qu’il faut tout faire soi-mˆeme. Stockage du code et gestion des ´etiquettes L’assembleur 269 Fichier stockage.mli #open "code";; exception Erreur of string;; value initialise: unit -> unit and assemble: instruction -> unit and poser_´etiquette: string -> unit and valeur_´etiquette: string -> int and extraire_code: unit -> instruction vect;; Pour r´esoudre les ´etiquettes, l’assembleur est oblig´e de fonctionner en deux ´etapes : dans la premi`ere ´etape il « pose » les ´etiquettes quand il les rencontre, c’est-`a-dire qu’il note leur adresse absolue dans une table d’association. Lorsqu’il rencontre `a nouveau l’´etiquette il la remplace par sa valeur. Malheureusement, cette phase ne suffit pas, car le programmeur peut faire r´ef´erence `a des ´etiquettes « en avant », c’est-`a-dire encore inconnues car pas encore rencontr´ees. C’est le cas de l’instruction jmp fact, r 31, qui est assembl´ee alors que l’´etiquette fact sera lue trois instructions plus loin. Dans ce cas, l’assembleur laisse l’adresse de l’´etiquette `a z´ero et note que l’instruction assembl´ee est `a compl´eter lorsque l’´etiquette sera connue. C’est le rˆole de la seconde phase d’assemblage que de repasser sur ces instructions incompl`etes et de les modifier avec les adresses d´esormais d´etermin´ees. La fonction r´esoudre_´etiquette se charge de ce travail (appel´e backpatching en anglais). Pour obtenir l’adresse absolue des ´etiquettes, l’assembleur g`ere un compteur ordinal virtuel, qu’il incr´emente `a chaque nouvelle instruction assembl´ee. Il engrange ses r´esultats dans un tableau d’instructions assembl´ees. Ces quantit´es font partie de l’´etat de l’assembleur, avec la table des ´etiquettes et la liste des ´etiquettes `a r´esoudre. La table des ´etiquettes est une table de hachage comme celles d´ecrite `a la section 12.8. Fichier stockage.ml #open "code";; type ´etat_de_l’assembleur = { mutable pc: int; mutable code: instruction vect; table_´etiq: (string, int) hashtbl__t; mutable `a_r´esoudre: (int * string) list };; let asm = { pc = 0; code = [||]; table_´etiq = hashtbl__new 17; `a_r´esoudre = [] };; let initialise () = asm.pc <- 0; asm.code <- make_vect 100 Stop; hashtbl__clear asm.table_´etiq; asm.`a_r´esoudre <- [];; let d´ecode_adresse adr = adr / taille_du_mot;; 270 Simulation d’un processeur let assemble instruction = if asm.pc >= vect_length asm.code then begin let nouveau_code = make_vect (2 * vect_length asm.code) Stop in blit_vect asm.code 0 nouveau_code 0 (vect_length asm.code); asm.code <- nouveau_code end; asm.code.(d´ecode_adresse asm.pc) <- instruction; asm.pc <- asm.pc + taille_du_mot;; let d´efinir_´etiquette nom_´etiq val_´etiq = try hashtbl__find asm.table_´etiq nom_´etiq; raise (Erreur ("´etiquette " ^ nom_´etiq ^ " red´efinie")) with Not_found -> hashtbl__add asm.table_´etiq nom_´etiq val_´etiq;; let poser_´etiquette nom_´etiq = d´efinir_´etiquette nom_´etiq asm.pc;; let valeur_´etiquette nom_´etiq = try hashtbl__find asm.table_´etiq nom_´etiq with Not_found -> asm.`a_r´esoudre <- (asm.pc, nom_´etiq) :: asm.`a_r´esoudre; 0;; La fonction assemble surveille le compteur ordinal virtuel : s’il d´eborde de la m´emoire programme virtuelle alors on remplace le tableau initial par un nouveau tableau deux fois plus long, dans lequel on recopie les instructions d´ej`a assembl´ees, et l’on continue normalement. (La recopie est effectu´ee par la fonction pr´ed´efinie blit_vect, qui est l’analogue pour les tableaux de la fonction blit_string pour les chaˆınes.) Il s’agit l`a d’une extension de la taille de la m´emoire virtuelle de l’assembleur : lorsque le code est compl`etement assembl´e, l’assembleur pourra d´eterminer exactement la taille du programme, qui sera celle qu’on attribuera `a l’ex´ecution. En effet, dans le pico-processeur (comme dans le monde r´eel), la m´emoire programme est fix´ee une fois pour toutes au lancement. Il n’est pas possible de changer la taille de la zone programme pendant que la pico-machine est en marche. D’ailleurs pourquoi en aurait-on besoin, puisqu’il s’agit alors d’ex´ecuter un certain programme fix´e. Nous d´etaillons maintenant le code de la fonction qui r´esout les ´etiquettes en modifiant les instructions o`u elles sont apparues alors qu’on ne connaissait pas encore leur valeur. Les ´etiquettes qui rep`erent une instruction dans la m´emoire programme peuvent apparaˆıtre dans les instructions de branchement, donc comme argument des instructions Jmp, Braz ou Branz. Ce sont les trois derniers cas du filtrage qui d´efinit la variable nouvelle_instruction, qui a ´evidemment pour valeur l’instruction provisoirement ´ecrite par l’assembleur, mais avec la valeur maintenant connue de l’´etiquette. Les ´etiquettes peuvent aussi apparaˆıtre dans des op´erations, comme deuxi`eme argument constant : en effet, il est parfois n´ecessaire de lire ou d’´ecrire en m´emoire des donn´ees l’adresse d’une instruction (par exemple pour ´ecrire directement l’adresse de L’assembleur 271 retour d’une fonction sur la pile). Fichier stockage.ml let r´esoudre_´etiquette (adresse, nom_´etiq) = let valeur = try hashtbl__find asm.table_´etiq nom_´etiq with Not_found -> raise (Erreur ("´etiquette " ^ nom_´etiq ^ " ind´efinie")) in let nouvelle_instruction = match asm.code.(d´ecode_adresse adresse) with | Op(op´eration, reg1, _, reg2) -> Op(op´eration, reg1, Imm valeur, reg2) | Jmp(_, reg) -> Jmp(Imm valeur, reg) | Braz(reg, _) -> Braz(reg, valeur) | Branz(reg, _) -> Branz(reg, valeur) | _ -> raise (Erreur "r´esoudre_´etiquette") in asm.code.(d´ecode_adresse adresse) <- nouvelle_instruction;; let extraire_code () = do_list r´esoudre_´etiquette asm.`a_r´esoudre; sub_vect asm.code 0 (d´ecode_adresse asm.pc);; Finalement, la fonction extraire_code appelle r´esoudre_´etiquette sur la liste des ´etiquettes non r´esolues, puis renvoie le tableau des instructions assembl´ees. (La fonction sub_vect est l’analogue pour les tableaux de la fonction sub_string des chaˆınes de caract`eres : elle extrait un sous-tableau d’une certaine longueur `a partir d’un certain indice.) La lecture et l’assemblage des programmes Le module lecture fournit l’unique fonction programme, qui lit un programme de la pico-machine depuis un flux de caract`eres, l’assemble, puis fait r´esoudre les ´etiquettes par la fonction extraire_code qui renvoie le tableau d’instructions correspondant. Fichier lecture.mli #open "code";; value programme: char stream -> instruction vect;; La lecture n’est pas tr`es complexe `a comprendre, si ce n’est qu’on ne construit pas d’arbre de syntaxe abstraite : rien ne nous y oblige ici, puisque nous n’analyserons pas les programmes assembl´es. On se contente donc d’assembler les instructions « au vol », d`es leur lecture, en laissant ´evidemment non r´esolues les r´ef´erences en avant. C’est la phase d’extraction du code qui se chargera ensuite de cette r´esolution. Pour l’analyse lexicale, nous r´eutilisons le g´en´erateur d’analyseurs lexicaux lexuniv introduit au chapitre 12, convenablement param´etr´e par la liste des mots-cl´es du langage d’assemblage. 272 Simulation d’un processeur Fichier lecture.ml #open "code";; #open "stockage";; #open "lexuniv";; let registre = function | [< ’MC "r"; ’Entier nbr >] -> nbr | [< ’MC "sp" >] -> sp | [< ’MC "ra" >] -> ra;; let constante = function | [< ’Entier nbr >] -> nbr | [< ’Ident nom_´etiq >] -> valeur_´etiquette nom_´etiq;; let op´erande = function | [< registre r >] -> Reg r | [< constante c >] -> Imm c;; let rec instruction = function | [< op´eration op; reg_op_reg (r1, o, r2) >] -> assemble(Op(op, r1, o, r2)) | [< test_invers´e test; reg_op_reg (r1, o, r2) >] -> assemble(Op(test, r1, o, r2)); assemble(Op(Seq, r2, Reg 0, r2)) | [< ’MC "jmp"; op´erande o; ’MC ","; registre r >] -> assemble(Jmp(o, r)) | [< ’MC "braz"; registre r; ’MC ","; constante c >] -> assemble(Braz(r, c)) | [< ’MC "branz"; registre r; ’MC ","; constante c >] -> assemble(Branz(r, c)) | [< ’MC "scall"; ’Entier n >] -> assemble (Scall n) | [< ’MC "write" >] -> assemble (Scall 1) | [< ’MC "read" >] -> assemble (Scall 0) | [< ’MC "stop" >] -> assemble Stop and reg_op_reg = function | [< registre r1; ’MC ","; op´erande o; ’MC ","; registre r2 >] -> (r1, o, r2) and op´eration = function | [< ’MC "load" >] -> Load | [< ’MC "store" >] -> Store | [< ’MC "add" >] -> Add | [< ’MC "mult" >] -> Mult | [< ’MC "sub" >] -> Sub | [< ’MC "div" >] -> Div | [< ’MC "and" >] -> And | [< ’MC "or" >] -> Or | [< ’MC "xor" >] -> Xor | [< ’MC "shl" >] -> Shl | [< ’MC "shr" >] -> Shr | [< ’MC "slt" >] -> Slt | [< ’MC "sle" >] -> Sle | [< ’MC "seq" >] -> Seq and test_invers´e = function | [< ’MC "sgt" >] -> Sle | [< ’MC "sge" >] -> Slt L’assembleur 273 | [< ’MC "sne" >] -> Seq;; let d´efinition_d’´etiquette = function | [< ’Ident nom_´etiq; ’MC ":" >] -> poser_´etiquette nom_´etiq;; let rec instruction_´etiq = function | [< d´efinition_d’´etiquette (); instruction_´etiq () >] -> () | [< instruction () >] -> ();; let rec suite_d’instructions flux = match flux with | [< instruction_´etiq () >] -> suite_d’instructions flux | [< >] -> ();; let analyseur_lexical = construire_analyseur ["r"; "sp"; "ra"; "load"; "store"; "add"; "mult"; "sub"; "div"; "and"; "or"; "xor"; "shl"; "shr"; "sgt"; "sge"; "sne"; "slt"; "sle"; "seq"; "jmp"; "braz"; "branz"; "scall"; "write"; "read"; "stop"; ","; ":"];; let programme flux = initialise (); suite_d’instructions (analyseur_lexical flux); extraire_code ();; L’assemblage d’un fichier complet L’assemblage d’un fichier consiste simplement `a le lire en m´emoire, `a l’assembler en r´esolvant les ´etiquettes, puis `a ´ecrire le tableau des instructions sur le fichier de sortie sp´ecifi´e. La seule difficult´e consiste `a g´erer les cas d’erreur. Fichier asm.ml let assemble_fichier nom_entr´ee nom_sortie = let entr´ee = open_in nom_entr´ee in let sortie = open_out_bin nom_sortie in try output_value sortie (lecture__programme (stream_of_channel entr´ee)); close_in entr´ee; close_out sortie; 0 with exc -> close_in entr´ee; close_out sortie; sys__remove nom_sortie; match exc with | Parse_error | Parse_failure -> prerr_string "Erreur de syntaxe aux alentours du caract`ere num´ero "; prerr_int (pos_in entr´ee); 274 Simulation d’un processeur prerr_endline ""; 1 | stockage__Erreur message -> prerr_string "Erreur d’assemblage: "; prerr_endline message; 1 | _ -> raise exc;; La fonction principale se contente d’analyser ses arguments, puis si tout va bien, elle appelle la fonction assemble_fichier pr´ec´edente. Fichier asm.ml exception Mauvais_arguments;; if sys__interactive then () else try if vect_length sys__command_line <> 3 then raise Mauvais_arguments; exit (assemble_fichier sys__command_line.(1) sys__command_line.(2)) with Mauvais_arguments -> prerr_endline "Usage: pico_asm "; exit 2 | sys__Sys_error message -> prerr_string "Erreur du syst`eme: "; prerr_endline message; exit 2;; Comme pour la commande pico_run on compile, puis assemble les modules de l’assembleur, pour produire la commande pico_asm : $ camlc -c stockage.mli $ camlc -c stockage.ml $ camlc -c lexuniv.mli $ camlc -c lexuniv.ml $ camlc -c lecture.mli $ camlc -c lecture.ml $ camlc -c asm.ml $ camlc -o pico_asm code.zo stockage.zo lexuniv.zo lecture.zo asm.zo Exemple Nous ex´ecutons la fonction factorielle `a l’aide de la pico-machine. Il nous faut assembler le fichier fact.asm avec la commande pico_asm, puis charger les instructions en m´emoire programme et lancer la pico-machine avec la commande pico_run : $ pico_asm fact.asm fact.o $ pico_run fact.o 10 3628800 Si l’on a compil´e et charg´e les modules depuis le syst`eme interactif, le mˆeme r´esultat s’obtient par les commandes asm__assemble_fichier "fact.asm" "fact.o" et exec__ex´ecute_fichier "fact.o" 4096. Pour aller plus loin 275 Adresse Instruction Assembleur source # Le programme principal 0 : Scall 0 read 4 : Jmp (Imm 16, 31) jmp fact, ra 8 : Scall 1 write 12 : Stop stop # La fonction fact(N) 16 : Braz (1, 60) fact: braz r 1, fact0 20 : Op (Sub, 30, Imm 8, 30) sub sp, 8, sp 24 : Op (Store, 30, Imm 0, 31) store sp, 0, ra 28 : Op (Store, 30, Imm 1, 1) store sp, 4, r 1 32 : Op (Sub, 1, Imm 1, 1) sub r 1, 1, r 1 36 : Jmp (Imm 16, 31) jmp fact, ra 40 : Op (Load, 30, Imm 1, 2) load sp, 4, r 2 44 : Op (Mult, 1, Reg 2, 1) mult r 1, r 2, r 1 48 : Op (Load, 30, Imm 0, 31) load sp, 0, ra 52 : Op (Add, 30, Imm 8, 30) add sp, 2, sp 56 : Jmp (Reg 31, 0) jmp ra, r 0 60 : Op (Add, 0, Imm 1, 1) fact0: add r 0, 1, r 1 64 : Jmp (Reg 31, 0) jmp ra, r 0 Figure 14.3: R´esultat de l’assemblage du fichier fact.asm `A titre d’exemple, nous donnons figure 14.3 le code assembl´e par la commande pico_asm pour le fichier fact.asm (page 268), en faisant figurer, en regard de chaque instruction assembl´ee, le code source correspondant du fichier. On constate sur cet exemple que les ´etiquettes ont ´et´e r´esolues correctement et que le registre sp est bien expans´e en son num´ero absolu. 14.4 Pour aller plus loin Le mod`ele de pico-processeur que nous avons d´ecrit n’est pas compl`etement r´ealiste : pour simplifier, nous n’avons pas rendu compte d’un certain nombre de traits des « vrais » processeurs, traits qui sont des cons´equences directes de l’architecture interne de ces processeurs. Par exemple, dans le processeurMIPS R3000 dont nous nous sommes inspir´es, certaines instructions prennent effet « `a retardement » : un branchement, par exemple, n’est pas ex´ecut´e imm´ediatement ; le processeur ex´ecute syst´ematiquement l’instruction qui suit le branchement avant de se d´erouter effectivement `a l’endroit indiqu ´e. Le pico-processeur ne simule pas ce fait. De mˆeme, nous n’avons pas essay´e de simuler fid`element le temps d’ex´ecution des programmes : mˆeme si, sur un processeur RISC, la plupart des instructions s’ex´ecutent en un cycle d’horloge, certaines instructions arithm´etiques (multiplication et division) prennent g´en´eralement plus de temps ; pis, les acc`es `a la m´emoire prennent des temps tr`es variables suivant qu’on tombe dans la m´emoire cache de niveau 1 (2 `a 3 cycles, typiquement), celle de niveau 2 (10 `a 30 cycles), dans la m´emoire principale (40 `a 100 cycles), ou dans la m´emoire virtuelle (des millions de cycles). Par cons´equent, le programme d´ecrit dans ce chapitre est davantage un interpr´eteur d’un langage d’assemblage raisonnablement r´ealiste qu’un simulateur 276 Simulation d’un processeur fid`ele d’un processeur r´eel. Simuler fid`element un processeur r´eel est un exercice de programmation int´eressant, quoique difficile. Bibliographie Pour une introduction progressive et tr`es compl`ete aux architectures de processeurs, on lira avec profit Architecture des ordinateurs : approche quantitative, de Hennessy et Patterson (International Thompson Publishing). 15 Compilation de mini-Pascal Un mini-Pascal pour une pico-machine, mais un programme respectable quand mˆeme. ne fois maˆıtris´ees les techniques de l’analyse syntaxique et de la manipulation d’arbres de syntaxe abstraite, il est naturel de les appliquer `a l’impl´ementation en Caml de v´eritables langages de programmation. Dans ce chapitre, nous ´ecrivons un compilateur pour un petit langage imp´eratif dans le style de Pascal, mais tr`es simplifi´e. Le code produit par le compilateur est ex´ecutable par le simulateur du chapitre 14. C’est l’occasion de montrer l’architecture g´en´erale d’un compilateur et d’introduire quelques algorithmes classiques de g´en´eration de code. Ce chapitre est ´egalement un bon exemple de structuration d’un programme assez complexe. 15.1 Syntaxe abstraite, syntaxe concr`ete Le langage auquel nous allons nous int´eresser est un sous-ensemble de Pascal. Les seuls types de donn´ees sont les entiers, les bool´eens et les tableaux `a indices entiers. Au niveau des instructions, certains types de boucles ont ´et´e omis. On dispose de proc´edures et de fonctions, mais elles ne peuvent pas ˆetre locales `a une autre proc´edure ou fonction. Les param`etres sont pass´es par valeur pour les entiers et les bool´eens et par r´ef´erence pour les tableaux. Derni`ere diff´erence majeure par rapport `a Pascal : les proc´edures et les fonctions sont consid´er´ees comme mutuellement r´ecursives ; on peut donc appeler une proc´edure avant de l’avoir d´efinie (comme en Modula-2). `A titre d’exemple, voici deux programmes mini-Pascal qui calculent la fonction de Fibonacci, de mani`ere plus ou moins na¨ıve. Fichier fib1.pas program fibonacci; var n: integer; function fib(n: integer): integer; begin if n < 2 then fib := 1 else fib := fib(n - 1) + fib(n - 2) end; begin read(n); write(fib(n)) 278 Compilation de mini-Pascal end Fichier fib2.pas program fibonacci; var fib: array [0 .. 100] of integer; var n: integer; var i: integer; begin read(n); fib[0] := 1; fib[1] := 1; i := 2; while i <= n do begin fib[i] := fib[i - 1] + fib[i - 2]; i := i + 1 end; write(fib[n]) end La syntaxe abstraite (c’est-`a-dire la repr´esentation interne) des programmes ´ecrits dans ce langage est structur´ee en plusieurs niveaux, correspondant chacun `a un type concret Caml : Niveau Type Caml Exemple Constantes constante true Expressions expression x+1 Instructions instruction x:=x+1 Expressions de type expr_type array [1..10] of integer D´eclarations de proc´edures d´ecl_proc procedure p(x:int). . . D´eclarations de fonctions d´ecl_fonc function f(x:int):int. . . Programmes programme program prog; . . . Ces types concrets sont d´efinis dans l’interface du module syntaxe. Fichier syntaxe.mli type constante = | Enti`ere of int | Bool´eenne of bool;; type expr_type = | Integer (* le type des entiers *) | Boolean (* le type des bool´eens *) | Array of int * int * expr_type;; (* le type des tableaux *) (* (les deux "int" sont les bornes) *) type expression = | Constante of constante | Variable of string | Application of string * expression list | Op_unaire of string * expression | Op_binaire of string * expression * expression | Acc`es_tableau of expression * expression;; type instruction = | Affectation_var of string * expression Syntaxe abstraite, syntaxe concr`ete 279 | Affectation_tableau of expression * expression * expression | Appel of string * expression list (* appel de proc´edure *) | If of expression * instruction * instruction | While of expression * instruction | Write of expression | Read of string | Bloc of instruction list;; (* bloc begin ... end *) type d´ecl_proc = { proc_param`etres: (string * expr_type) list; proc_variables: (string * expr_type) list; proc_corps: instruction } and d´ecl_fonc = { fonc_param`etres: (string * expr_type) list; fonc_type_r´esultat: expr_type; fonc_variables: (string * expr_type) list; fonc_corps: instruction };; type programme = { prog_variables: (string * expr_type) list; prog_proc´edures: (string * d´ecl_proc) list; prog_fonctions: (string * d´ecl_fonc) list; prog_corps: instruction };; value lire_programme : char stream -> programme;; L’impl´ementation du module syntaxe est enti`erement consacr´ee `a l’analyseur syntaxique (la fonction lire_programme d´eclar´ee ci-dessus). Nous r´eutilisons l’analyseur lexical « universel » lexuniv introduit au chapitre 12 pour la lecture des propositions et utilis´e ´egalement au chapitre 14 pour l’assembleur de la pico-machine. Fichier syntaxe.ml #open "lexuniv";; let analyseur_lexical = construire_analyseur ["false";"true";"("; ","; ")"; "["; "]"; "not"; "*"; "/"; "-"; "+"; "="; "<>"; "<"; ">"; "<="; ">="; "and"; "or"; "if"; "then"; "else"; "while"; "do"; "write"; "read"; "begin"; ";"; "end"; ":="; "integer"; "boolean"; "array"; "of"; ".."; "var"; ":"; "procedure"; "function"; "program"];; L’analyseur lexical s’obtient par application partielle de la fonction construire_ analyseur `a la liste des mots-cl´es. Viennent ensuite deux puissantes fonctionnelles d’analyse syntaxique, l’une pour analyser des listes, l’autre pour analyser des applications d’op´erateurs infixes. Voici la fonctionnelle d’analyse des listes. Fichier syntaxe.ml let lire_liste lire_´el´ement s´eparateur = let rec lire_reste = function | [< (stream_check (function lex`eme -> lex`eme = MC s´eparateur)) s´ep; lire_´el´ement elt; 280 Compilation de mini-Pascal lire_reste reste >] -> elt :: reste | [< >] -> [] in function [< lire_´el´ement elt; lire_reste reste >] -> elt :: reste | [< >] -> [];; La fonctionnelle lire_liste prend en argument un analyseur lire_´el´ement et une chaˆıne s´eparateur et renvoie un analyseur qui reconnaˆıt les listes d’´el´ements reconnus par lire_´el´ements, s´epar´es par des occurrences de la chaˆıne s´eparateur. Par exemple, lire_liste (function [< ’Entier n >] -> n) "," est une fonction de type lex`eme stream -> int list qui reconnaˆıt les listes d’entiers s´epar´es par des virgules. En appliquant cette fonction au flux [< ’Entier 1; ’MC ","; ’Entier 2 >], on obtient la liste [1;2]. La fonction stream_check employ´ee dans lire_reste permet de filtrer les caract` eres qui v´erifient une certaine condition. De mani`ere g´en´erale, un motif de la forme [< (stream_check p) c; ... >] est s´electionn´e si la fonction p appliqu´ee au premier ´el´ement du flux renvoie true. Le premier ´el´ement du flux est alors li´e `a la variable c et le filtrage continue comme d’habitude. Dans le cas de lire_reste, le motif [< (stream_check (function lex`eme -> lex`eme = MC s´eparateur)) s´ep >] filtre donc les mots-cl´es dont le texte est identique `a la chaˆıne s´eparateur. L’autre fonctionnelle sert `a analyser les applications d’op´erateurs binaires infixes, comme + ou and. Fichier syntaxe.ml let est_un_op´erateur op´erateurs = function | MC op -> mem op op´erateurs | _ -> false;; let lire_op´erateur op´erateurs = function | [< (stream_check (est_un_op´erateur op´erateurs)) (MC op) >] -> op;; let lire_op´eration lire_base op´erateurs = let rec lire_reste e1 = function | [< (lire_op´erateur op´erateurs) op; lire_base e2; (lire_reste (Op_binaire(op, e1, e2))) e >] -> e | [< >] -> e1 in function [< lire_base e1; (lire_reste e1) e >] -> e;; La fonction lire_op´erateur prend en argument une liste d’op´erateurs repr´esent´es par des chaˆınes de caract`eres, comme ["+";"-"], et rend un analyseur reconnaissant les flux qui commencent par un de ces op´erateurs. La fonctionnelle lire_op´eration prend en arguments un analyseur pour les expressions simples et une liste d’op´erateurs et reconnaˆıt les applications de ces op´erateurs `a des expressions simples. Par exemple, lire_op´eration (function [< ’Entier n >] -> Constante(Enti`ere n)) ["+";"-"] renvoie un analyseur de type lex`eme stream -> expression reconnaissant des expressions comme 1+2-3. En appliquant cet analyseur au flux Syntaxe abstraite, syntaxe concr`ete 281 [< ’Entier 1; ’MC "+"; ’Entier 2; ’MC "-"; ’Entier 3 >] on obtient l’expression Op_binaire("-", Op_binaire("+", Constante(Enti`ere 1), Constante(Enti`ere 2)), Constante(Enti`ere 3)) Pour construire l’arbre de syntaxe, on consid`ere que les op´erateurs sont associatifs `a gauche ; autrement dit, 1+2-3 est lue comme (1+2)-3 et non pas comme 1+(2-3). Le reste de l’analyseur syntaxique est plus simple `a comprendre. Pour les expressions, nous introduisons un certain nombre d’analyseurs interm´ediaires, afin de respecter les priorit´es usuelles entre op´erateurs : * est plus prioritaire que +, and est plus prioritaire que or, etc. Fichier syntaxe.ml let rec lire_expr0 flux = match flux with | [< ’Entier n >] -> Constante(Enti`ere n) | [< ’MC "false" >] -> Constante(Bool´eenne false) | [< ’MC "true" >] -> Constante(Bool´eenne true) | [< ’Ident nom >] -> begin match flux with | [< ’MC "("; (lire_liste lire_expr ",") el; ’MC ")">] -> Application(nom, el) | [< >] -> Variable nom end | [< ’MC "("; lire_expr e; ’MC ")" >] -> e and lire_expr1 flux = match flux with [< lire_expr0 e1 >] -> match flux with | [< ’MC "["; lire_expr e2; ’MC "]" >] -> Acc`es_tableau(e1,e2) | [< >] -> e1 and lire_expr2 = function | [< ’MC "-"; lire_expr1 e >] -> Op_unaire("-", e) | [< ’MC "not"; lire_expr1 e >] -> Op_unaire("not", e) | [< lire_expr1 e >] -> e and lire_expr3 flux = lire_op´eration lire_expr2 ["*"; "/"] flux and lire_expr4 flux = lire_op´eration lire_expr3 ["+"; "-"] flux and lire_expr5 flux = lire_op´eration lire_expr4 ["="; "<>"; "<"; ">"; "<="; ">="] flux and lire_expr6 flux = lire_op´eration lire_expr5 ["and"] flux and lire_expr flux = lire_op´eration lire_expr6 ["or"] flux;; Viennent ensuite des analyseurs pour les instructions, les types, les d´eclarations 282 Compilation de mini-Pascal de variables, les d´eclarations de fonctions, les d´eclarations de proc´edures et enfin les programmes. Fichier syntaxe.ml let rec lire_instr flux = match flux with | [< ’MC "if"; lire_expr e1; ’MC "then"; lire_instr i2 >] -> begin match flux with | [< ’MC "else"; lire_instr i3 >] -> If(e1, i2, i3) | [< >] -> If(e1, i2, Bloc []) end | [< ’MC "while"; lire_expr e1; ’MC "do"; lire_instr i2 >] -> While(e1, i2) | [< ’MC "write"; ’MC "("; lire_expr e; ’MC ")" >] -> Write e | [< ’MC "read"; ’MC "("; ’Ident nom; ’MC ")" >] -> Read nom | [< ’MC "begin"; (lire_liste lire_instr ";") il; ’MC "end" >] -> Bloc il | [< lire_expr e >] -> match e with | Application(nom, el) -> Appel(nom, el) | Variable nom -> begin match flux with | [< ’MC ":="; lire_expr e >] -> Affectation_var(nom, e) end | Acc`es_tableau(e1, e2) -> begin match flux with | [< ’MC ":="; lire_expr e3 >] -> Affectation_tableau(e1, e2, e3) end | _ -> raise Parse_error;; let rec lire_type = function | [< ’MC "integer" >] -> Integer | [< ’MC "boolean" >] -> Boolean | [< ’MC "array"; ’MC "["; ’Entier bas; ’MC ".."; ’Entier haut; ’MC "]"; ’MC "of"; lire_type ty >] -> Array(bas, haut, ty);; let rec lire_variables = function | [< ’MC "var"; ’Ident nom; ’MC ":"; lire_type ty; ’MC ";"; lire_variables reste >] -> (nom,ty)::reste | [< >] -> [];; let lire_un_param`etre = function | [< ’Ident nom; ’MC ":"; lire_type ty >] -> (nom,ty);; let lire_param`etres = function | [< ’MC "("; (lire_liste lire_un_param`etre ",") param`etres; Typage 283 ’MC ")" >] -> param`etres;; let lire_proc´edure = function | [< ’MC "procedure"; ’Ident nom; lire_param`etres p; ’MC ";"; lire_variables v; lire_instr i; ’MC ";" >] -> (nom, {proc_param`etres=p; proc_variables=v; proc_corps=i});; let lire_fonction = function | [< ’MC "function"; ’Ident nom; lire_param`etres p; ’MC ":"; lire_type ty; ’MC ";"; lire_variables v; lire_instr i; ’MC ";" >] -> (nom, {fonc_param`etres=p; fonc_type_r´esultat=ty; fonc_variables=v; fonc_corps=i});; let rec lire_proc_fonc = function | [< lire_proc´edure proc; lire_proc_fonc (procs, foncs) >] -> (proc::procs, foncs) | [< lire_fonction fonc; lire_proc_fonc (procs, foncs) >] -> (procs, fonc::foncs) | [< >] -> ([], []);; let lire_prog = function | [< ’MC "program"; ’Ident nom_du_programme; ’MC ";"; lire_variables v; lire_proc_fonc (p,f); lire_instr i >] -> { prog_variables=v; prog_proc´edures=p; prog_fonctions=f; prog_corps=i };; let lire_programme flux = lire_prog (analyseur_lexical flux);; 15.2 Typage Nous programmons maintenant un v´erificateur de types pour les programmes mini- Pascal. Le but premier du v´erificateur de types est de garantir l’absence d’incoh´erences entre les types des objets manipul´es par le programme ; par exemple, l’addition d’un entier et d’un tableau doit ˆetre rejet´ee. Un but secondaire est de d´etecter certaines op´erations comme l’affectation entre tableaux ou le renvoi d’un tableau comme r´esultat d’une fonction, op´erations que nous avons d´ecid´e d’interdire pour faciliter la compilation du langage. De mani`ere g´en´erale, les erreurs de types peuvent ˆetre d´etect´ees ou bien au moment de l’ex´ecution (typage dynamique), ou bien avant l’ex´ecution, par une analyse pr´ealable du programme (typage statique). Le typage statique offre plusieurs avantages par rapport au typage dynamique. Tout d’abord, il d´etecte les erreurs de types dans toutes les branches du programme, mˆeme celles qui ne sont pas toujours ex´ecut´ees. De plus, dans le cadre d’un compilateur, le typage statique nous autorise `a produire du code ne contenant aucun test sur le type des objets, puisqu’il garantit que les objets manipul´es pendant l’ex´ecution seront forc´ement du bon type pour les op´erations effectu´ees dessus. Au contraire, pour faire du typage dynamique, il faudrait produire du code pour v´erifier les types `a l’ex´ecution, ce qui complique la compilation et ralentit l’ex´ecution. 284 Compilation de mini-Pascal Voici l’interface du module typage fournissant la fonction de v´erification des types. Fichier typage.mli #open "syntaxe";; type erreur_de_type = | Ind´efini of string (* variable utilis´ee mais non d´efinie *) | Conflit of string * expr_type * expr_type (* conflit de types *) | Arit´e of string * int * int (* mauvais nombre d’arguments *) | Tableau_attendu (* [..] appliqu´e `a un non-tableau *) | Tableau_interdit of string;; (* tableau renvoy´e en r´esultat *) exception Erreur_typage of erreur_de_type;; value type_programme: programme -> unit and affiche_erreur: erreur_de_type -> unit and type_op_unaire: string -> expr_type * expr_type and type_op_binaire: string -> expr_type * expr_type * expr_type;; La fonction type_programme signale les erreurs de typage en d´eclenchant alors l’exception Erreur_typage avec pour argument une description de l’erreur. L’erreur peut ensuite ˆetre imprim´ee par la fonction affiche_erreur. Environnements de typage Pour typer une expression ou une instruction, il est n´ecessaire de connaˆıtre les types des variables, des fonctions et des proc´edures mentionn´ees dans cette expression ou cette instruction. L’environnement de typage est une structure de donn´ees qui associe aux noms de variables leur type courant, c’est-`a-dire le type de la d´eclaration la plus r´ecente de chaque variable. Il associe ´egalement aux noms de proc´edures et de fonctions leurs d´eclarations (noms des param`etres, corps de la proc´edure, etc.). Le module envir fournit le type abstrait env des environnements et les op´erations de base sur ce type. Pour pouvoir r´eutiliser le module envir, nous allons param´etrer le type env par le type des informations associ´ees aux variables. Le type fourni par le module envir est donc ’a env, o`u ’a est le type des informations associ´ees aux variables. Dans le v´erificateur, nous associons des expressions de types aux variables, et utilisons donc des environnements de type expr_type env. Dans le compilateur (section 15.3), ce sont des informations de compilation que nous associerons aux variables. Fichier envir.mli #open "syntaxe";; type ’a env;; value environnement_initial: (string * d´ecl_proc) list -> (string * d´ecl_fonc) list -> ’a env and ajoute_variable: string -> ’a -> ’a env -> ’a env and cherche_variable: string -> ’a env -> ’a and cherche_fonction: string -> ’a env -> d´ecl_fonc and cherche_proc´edure: string -> ’a env -> d´ecl_proc;; exception Pas_trouv´e of string;; En voici une impl´ementation simple, `a base de listes d’associations. Typage 285 Fichier envir.ml #open "syntaxe";; #open "interp";; type ’a env = { vars: (string * ’a) list; procs: (string * d´ecl_proc) list; foncs: (string * d´ecl_fonc) list };; let environnement_initial p f = { vars=[]; procs=p; foncs=f };; let ajoute_variable nom info env = { vars=(nom,info)::env.vars; procs=env.procs; foncs=env.foncs };; let cherche nom liste = try assoc nom liste with Not_found -> raise(Pas_trouv´e nom);; let cherche_variable nom env = cherche nom env.vars and cherche_fonction nom env = cherche nom env.foncs and cherche_proc´edure nom env = cherche nom env.procs;; Typage des expressions L’impl´ementation du module typage commence par trois fonctions ´el´ementaires de v´erification sur les types. Fichier typage.ml #open "syntaxe";; #open "envir";; let v´erifie_type message type_attendu type_r´eel = if type_attendu <> type_r´eel then raise(Erreur_typage(Conflit(message, type_attendu, type_r´eel)));; let v´erifie_tableau = function | Array(inf, sup, ´el´ements) -> ´el´ements | _ -> raise(Erreur_typage(Tableau_attendu));; let v´erifie_non_tableau message = function | Array(inf, sup, ´el´ements) -> raise(Erreur_typage(Tableau_interdit message)) | _ -> ();; Passons ensuite `a la fonction type_expr, qui calcule le type d’une expression, ou d´eclenche l’exception Erreur_typage si l’expression est mal typ´ee. Cette fonction prend en argument un environnement qui fait correspondre aux variables leur type, aux proc´edures et aux fonctions leur d´eclaration. Fichier typage.ml let rec type_expr env = function | Constante(Enti`ere n) -> Integer 286 Compilation de mini-Pascal | Constante(Bool´eenne b) -> Boolean | Variable nom_var -> cherche_variable nom_var env | Application(nom_fonc, args) -> let fonc = cherche_fonction nom_fonc env in type_application env nom_fonc fonc.fonc_param`etres args; fonc.fonc_type_r´esultat | Op_unaire(op, arg) -> let (type_arg, type_res) = type_op_unaire op in v´erifie_type ("l’argument de " ^ op) type_arg (type_expr env arg); type_res | Op_binaire(op, arg1, arg2) -> let (type_arg1, type_arg2, type_res) = type_op_binaire op in v´erifie_type ("le premier argument de " ^ op) type_arg1 (type_expr env arg1); v´erifie_type ("le deuxi`eme argument de " ^ op) type_arg2 (type_expr env arg2); type_res | Acc`es_tableau(expr1, expr2) -> let type_´el´ements = v´erifie_tableau (type_expr env expr1) in v´erifie_type "l’indice de tableau" Integer (type_expr env expr2); type_´el´ements and type_application env nom param`etres arguments = let nbr_param`etres = list_length param`etres and nbr_arguments = list_length arguments in if nbr_param`etres <> nbr_arguments then raise(Erreur_typage(Arit´e(nom, nbr_param`etres, nbr_arguments))); let type_param`etre (nom_param, type_param) argument = v´erifie_type ("le param`etre " ^ nom_param ^ " de " ^ nom) type_param (type_expr env argument) in do_list2 type_param`etre param`etres arguments and type_op_unaire = function | "-" -> (Integer, Integer) | "not" -> (Boolean, Boolean) and type_op_binaire = function | "*" | "/" | "+" | "-" -> (Integer,Integer,Integer) | "=" | "<>" | "<" | ">" | "<=" | ">=" -> (Integer,Integer,Boolean) | "and" | "or" -> (Boolean,Boolean,Boolean);; Typage des instructions L’´etape suivante consiste `a typer les instructions. Au contraire de type_expr, la fonction type_instr ne renvoie rien : il n’y a pas de type `a calculer, seulement des types `a v´erifier. Typage 287 Fichier typage.ml let rec type_instr env = function | Affectation_var(nom_var, expr) -> let type_var = cherche_variable nom_var env in v´erifie_non_tableau ("affectation de " ^ nom_var) type_var; v´erifie_type ("la variable " ^ nom_var) type_var (type_expr env expr) | Affectation_tableau(expr1, expr2, expr3) -> let type_´el´ements = v´erifie_tableau (type_expr env expr1) in v´erifie_non_tableau "affectation de tableau" type_´el´ements; v´erifie_type "l’indice de tableau" Integer (type_expr env expr2); v´erifie_type "affectation de tableau" type_´el´ements (type_expr env expr3) | Appel(nom_proc, args) -> let proc = cherche_proc´edure nom_proc env in type_application env nom_proc proc.proc_param`etres args | If(condition, branche_oui, branche_non) -> v´erifie_type "la condition de IF" Boolean (type_expr env condition); type_instr env branche_oui; type_instr env branche_non | While(condition, corps) -> v´erifie_type "la condition de WHILE" Boolean (type_expr env condition); type_instr env corps | Write expr -> v´erifie_type "l’argument de WRITE" Integer (type_expr env expr) | Read nom_var -> v´erifie_type "l’argument de READ" Integer (cherche_variable nom_var env) | Bloc liste -> do_list (type_instr env) liste;; Typage d’un programme Les fonctions de typage d’une d´eclaration de fonction ou de proc´edure ajoutent dans l’environnement les types d´eclar´es pour les param`etres et les variables locales, puis v´erifient dans cet environnement le typage du corps de la fonction ou de la proc´edure. Fichier typage.ml let ajoute_var (nom, typ) env = ajoute_variable nom typ env;; let type_proc´edure env_global (nom, d´ecl) = let env = list_it ajoute_var (d´ecl.proc_variables @ d´ecl.proc_param`etres) env_global in type_instr env d´ecl.proc_corps;; 288 Compilation de mini-Pascal let type_fonction env_global (nom, d´ecl) = v´erifie_non_tableau ("passage comme r´esultat de la fonction " ^ nom) d´ecl.fonc_type_r´esultat; let env = list_it ajoute_var ((nom, d´ecl.fonc_type_r´esultat) :: d´ecl.fonc_variables @ d´ecl.fonc_param`etres) env_global in type_instr env d´ecl.fonc_corps;; Enfin, le typage d’un programme tout entier consiste `a construire un environnement de typage global, correspondant aux d´eclarations de variables globales, de fonctions et de proc´edures, puis `a v´erifier les types dans les fonctions, les proc´edures et le corps du programme. Fichier typage.ml let type_programme prog = let env_global = list_it ajoute_var prog.prog_variables (environnement_initial prog.prog_proc´edures prog.prog_fonctions) in try do_list (type_proc´edure env_global) prog.prog_proc´edures; do_list (type_fonction env_global) prog.prog_fonctions; type_instr env_global prog.prog_corps with Pas_trouv´e nom -> raise(Erreur_typage(Ind´efini nom));; Traitement des erreurs de typage Il reste `a afficher les messages d’erreur. On utilise pour cela les fonctions de biblioth` eque prerr_int et prerr_string de pr´ef´erence `a print_string et print_int, car les premi`eres affichent sur la sortie d’erreur standard et non sur la sortie standard comme les secondes. Fichier typage.ml let rec affiche_type = function | Integer -> prerr_string "integer" | Boolean -> prerr_string "boolean" | Array(inf, sup, ty) -> prerr_string "array ["; prerr_int inf; prerr_string ".."; prerr_int sup; prerr_string "] of "; affiche_type ty;; let affiche_erreur = function | Ind´efini nom -> prerr_string "Nom inconnu: "; prerr_string nom; prerr_endline "." | Conflit(message, type_attendu, type_r´eel) -> prerr_string "Conflit de types: "; prerr_string message; prerr_string " devrait avoir le type "; Compilation 289 affiche_type type_attendu; prerr_string " mais a le type "; affiche_type type_r´eel; prerr_endline "." | Arit´e(nom, nbr_param`etres, nbr_arguments) -> prerr_string "Mauvais nombre d’arguments: "; prerr_string nom; prerr_string " attend "; prerr_int nbr_param`etres; prerr_string " param`etre(s), mais est appel´ee avec "; prerr_int nbr_arguments; prerr_endline " argument(s)." | Tableau_attendu -> prerr_endline "Acc`es dans un objet qui n’est pas un tableau." | Tableau_interdit message -> prerr_string "Op´eration interdite sur les tableaux: "; prerr_string message; prerr_endline ".";; Les textes des messages d’erreur sont assez descriptifs. Il leur manque n´eanmoins une information fort utile : le num´ero de la ligne o`u l’erreur s’est produite. Cette information ne figure pas dans l’arbre de syntaxe abstraite. Pour produire de meilleurs messages d’erreur, une premi`ere possibilit´e est de faire le typage en mˆeme temps que l’analyse syntaxique, auquel cas on irait simplement consulter une variable globale contenant le num´ero de la ligne en cours d’analyse, variable tenue `a jour par l’analyseur lexical. Cette solution est simple, mais complique la structure du compilateur. Une autre solution, plus g´en´erale, est de travailler sur un arbre de syntaxe abstraite annot´e par les num´eros de lignes correspondants dans le texte source. Par exemple, pour annoter chaque instruction, il faudrait d´eclarer le type instruction du module syntaxe comme suit : type instruction = { description: descr_instruction; ligne: int } and descr_instruction = | Affectation_var of string * expression | If of expression * instruction * instruction ... Chaque noeud du type instruction dans l’arbre de syntaxe abstraite est ainsi annot´e par un entier : le num´ero de ligne. Nous n’avons pas utilis´e cette technique dans ce chapitre, car elle alourdit d´esagr´eablement toutes les fonctions qui op`erent sur l’arbre de syntaxe abstraite. C’est cependant une technique tr`es g´en´erale, qui peut servir pour bien d’autres types d’annotations en plus des num´eros de lignes : types des expressions, informations de compilation, informations de mise au point (debugging). 15.3 Compilation Cette section pr´esente un compilateur pour le langage mini-Pascal, produisant du code pour le pico-processeur d´ecrit dans le chapitre 14. Le compilateur se pr´esente sous la forme d’un module compil, dont l’interface est fort simple (au contraire de l’impl´ementation . . . ). 290 Compilation de mini-Pascal Fichier compil.mli value compile_programme: syntaxe__programme -> unit;; La fonction compile_programme ´ecrit directement sur la sortie standard le code assembleur pour le programme donn´e en argument. Repr´esentation des donn´ees Commen¸cons par d´efinir comment les types de donn´ees mini-Pascal sont repr´esent´es en machine. Les entiers (type integer) sont directement repr´esent´es par les entiers de la machine. Pour les bool´eens (type boolean), nous repr´esentons false par l’entier 0 et true par l’entier 1. Un tableau (type array) est stock´e dans un certain nombre de mots m´emoire contigus et repr´esent´e par l’adresse m´emoire du premier ´el´ement. Dans le cas d’un tableau de tableaux (c’est-`a-dire un tableau multidimensionnel), deux approches sont possibles : l’allocation structur´ee ou l’allocation `a plat. Avec l’allocation structur´ee, une valeur de type array [1..2] of array [1..2] of integer est repr´esent´ee par un tableau de deux pointeurs vers deux tableaux de deux entiers. Avec l’allocation `a plat, une valeur du mˆeme type est un bloc m´emoire de quatre mots, contenant les deux tableaux de deux entiers mis cˆote `a cˆote. • • t[1][1] t[1][2] t[2][1] t[2][2] t Allocation structur´ee t[1][1] t[1][2] t[2][1] t[2][2] t Allocation `a plat Nous allons utiliser l’allocation `a plat, qui est plus compacte et plus efficace `a l’acc`es que l’allocation structur´ee, bien qu’elle complique l´eg`erement la compilation. En particulier, pour calculer le d´ecalage entre le d´ebut d’un tableau et l’´el´ement auquel on veut acc´eder, il faut connaˆıtre `a la compilation la taille des ´el´ements du tableau. Heureusement, cette taille se d´eduit facilement du type des ´el´ements du tableau, qui est une information connue `a la compilation. La fonction taille_du_type ci-dessous calcule le nombre d’octets occup´es par la repr´esentation d’un objet du type donn´e. Fichier compil.ml #open "syntaxe";; #open "envir";; #open "printf";; let taille_du_mot = 4;; (* un mot = quatre octets *) let rec taille_du_type = function | Integer | Boolean -> taille_du_mot Compilation 291 | Array(inf, sup, ty) -> (sup - inf + 1) * taille_du_type ty;; La fonction val_const traduit une constante en l’entier qui la repr´esente. Fichier compil.ml let val_const = function | Enti`ere n -> n | Bool´eenne b -> if b then 1 else 0;; Environnements de compilation Les fonctions de compilation prennent en argument un environnement qui indique `a quels emplacements m´emoire se trouvent les variables. On r´eutilise la structure g´en´erique d’environnement fournie par le module envir. Les donn´ees associ´ees aux noms de variables sont du type info_variable d´efini ci-dessous. Fichier compil.ml type info_variable = { typ: expr_type; emplacement: emplacement_variable } and emplacement_variable = | Global_indirect of int | Global_direct of int | Local_indirect of int | Local_direct of int;; Le compilateur attribue une adresse absolue `a chaque variable globale. Cette adresse est l’argument des constructeurs Global_direct et Global_indirect. La distinction entre ces deux constructeurs est la suivante : si la variable est un entier ou un bool´een, l’entier associ´e est l’adresse d’un mot qui contient la valeur courante de x. En revanche, si la variable est un tableau, l’entier associ´e est l’adresse du premier mot du bloc m´emoire correspondant ; la variable s’´evalue donc en l’adresse elle-mˆeme et non pas en le mot contenu `a cette adresse. L’emplacement Global_indirect a correspond au premier cas (une indirection `a partir de a est n´ecessaire) ; l’emplacement Global_direct a correspond au deuxi`eme cas (pas d’indirection `a partir de a). a valeur de x var x : integer valeur de x[1] valeur de x[2] valeur de x[3] a valeur de x var x : array [1..3] of integer Les variables locales des fonctions et des proc´edures, ainsi que leurs param`etres, sont stock´ees sur la pile. Chaque fonction ou proc´edure s’alloue, quand elle est appel´ee, un bloc dans la pile appel´e bloc d’activation (activation record ou stack frame, dans la litt´erature). Le bloc d’activation contient les param`etres, les variables locales et l’adresse de retour `a l’appelant. Les blocs d’activation ont la structure suivante (la pile croˆıt vers le bas) : 292 Compilation de mini-Pascal dernier param`etre ... premier param`etre adresse de retour variables locales temporaires pointeur de pile Les blocs d’activation s’empilent les uns derri`ere les autres au gr´e des appels de fonctions et de proc´edures. Par exemple, si g appelle f, voici l’´etat de la pile pendant que f s’ex´ecute : param`etres de g adresse de retour variables locales de g temporaires de g param`etres pass´es `a f par g adresse de retour dans g variables locales de f temporaires de f pointeur de pile L’entier argument des constructeurs Local_indirect et Local_direct repr´esente la position relative de la variable locale dans le bloc d’activation. Le point de rep`ere est l’adresse du mot contenant le premier param`etre. Les positions positives correspondent aux variables locales ; les positions n´egatives, aux param`etres. Comme pour les valeurs globales, Local_indirect signifie qu’il faut faire une indirection `a cette adresse pour obtenir la valeur de la variable, alors que Local_direct signifie que cette adresse est la valeur de la variable. param`etres adresse de retour variable x position relative de x !profondeur_pile pointeur de pile L’acc`es aux variables locales se fait par adressage relatif au pointeur de pile (le registre num´ero 30, aussi appel´e sp). Le compilateur garde trace du d´ecalage entre le pointeur de pile et le mot contenant l’adresse de retour dans le bloc d’activation courant, dans la variable profondeur_pile. Cette variable augmente lorsqu’on alloue de nouveaux temporaires et diminue quand on les lib`ere. Fichier compil.ml let profondeur_pile = ref 0;; Compilation 293 let r´eserve_pile n = printf "sub sp, %d, sp\n" (n * taille_du_mot); profondeur_pile := !profondeur_pile + n * taille_du_mot and lib`ere_pile n = printf "add sp, %d, sp\n" (n * taille_du_mot); profondeur_pile := !profondeur_pile - n * taille_du_mot;; Les fonctions r´eserve_pile et lib`ere_pile ´emettent du code machine qui modifie le registre pointeur de pile et modifient profondeur_pile pour que le code ´emis pour les acc`es aux variables relativement `a ce registre reste correct. Le code est ´emis sur la sortie standard `a l’aide de la fonction printf du module de biblioth`eque printf. Tout comme la fonction sprintf que nous avons rencontr´ee dans la section 11.2, printf prend en argument une chaˆıne de format et un certain nombre d’entiers ou de chaˆınes de caract`eres, et remplace dans le format les s´equences de la forme % plus une lettre par le prochain argument. Au contraire de sprintf, qui renvoie le r´esultat du formatage sous forme de chaˆıne, printf l’affiche directement sur la sortie standard. Par exemple, printf "add r %d, %s, r %d" 1 "´etiquette" 2 affiche add r 1, ´etiquette, r 2 La lettre suivant % indique le type de l’argument `a afficher et le format d’affichage `a utiliser. Parmi les nombreux formats existants, nous n’en utiliserons que deux : %d, qui affiche un entier en d´ecimal, et %s, qui affiche une chaˆıne de caract`eres. Fonctions d’analyse des expressions Nous passons maintenant `a des fonctions auxiliaires qui calculent certaines propri ´et´es des expressions. La premi`ere calcule le type d’une expression de tableau, c’est- `a-dire les bornes inf´erieures et sup´erieures du tableau, ainsi que le type des ´el´ements ; cette derni`ere information d´etermine la taille des ´el´ements lors d’un acc`es au tableau. Fichier compil.ml let rec type_de_tableau env = function | Variable nom -> begin match cherche_variable nom env with { typ = Array(inf, sup, ty) } -> (inf, sup, ty) end | Acc`es_tableau(arg1, arg2) -> match type_de_tableau env arg1 with (_, _, Array(inf, sup, ty)) -> (inf, sup, ty);; Comme les tableaux ne peuvent ˆetre renvoy´es comme r´esultat d’un appel de fonction, une expression bien typ´ee de type Array ne peut ˆetre qu’une variable ou un acc`es dans un tableau multidimensionnel ; ces deux cas sont donc les seuls `a consid´erer dans type_de_tableau. La deuxi`eme fonction auxiliaire d´etermine si l’´evaluation d’une expression peut « interf´erer » avec l’´evaluation d’une autre. Deux expressions n’interf`erent pas si leurs 294 Compilation de mini-Pascal r´esultats et les effets qu’elles effectuent ne d´ependent pas de l’ordre dans lequel on les ´evalue. Une expression qui contient un appel de fonction est susceptible d’interf´erer avec une autre expression : la fonction peut ´ecrire quelque chose `a l’´ecran, ou modifier une variable globale. De mˆeme, une expression qui fait intervenir la valeur d’une variable globale peut interf´erer avec une autre expression qui va modifier cette variable globale `a l’occasion d’un appel de fonction. On se convainc facilement qu’une expression ne contenant ni variables globales, ni appels de fonctions n’interf`ere avec aucune autre expression. (Pour rester simple, on suppose ici que tout appel de fonction est susceptible de faire des effets. Pour faire les choses plus finement, il faudrait analyser aussi le corps de la fonction appel´ee, pour voir s’il peut faire des effets en s’ex´ecutant.) Fichier compil.ml let rec sans_interf´erences env = function | Constante c -> true | Variable nom -> let var = cherche_variable nom env in begin match var.emplacement with | Global_indirect _ | Global_direct _ -> false | Local_indirect _ | Local_direct _ -> true end | Application(fonc, args) -> false | Op_unaire(op, arg) -> sans_interf´erences env arg | Op_binaire(op, arg1, arg2) -> sans_interf´erences env arg1 && sans_interf´erences env arg2 | Acc`es_tableau(arg1, arg2) -> sans_interf´erences env arg1 && sans_interf´erences env arg2;; La derni`ere fonction auxiliaire d´etermine les « besoins en registres » d’une expression : de combien de registres libres l’´evaluation de l’expression a besoin pour stocker ses r´esultats interm´ediaires. Cette information est pr´ecieuse pour d´eterminer quand il est n´ecessaire de sauvegarder des r´esultats interm´ediaires sur la pile. La strat´egie que nous allons employer est la suivante : avant d’´evaluer une expression, on regarde si ses besoins en registres sont sup´erieurs au nombre de registres inutilis ´es (c’est-`a-dire ne contenant pas d´ej`a un r´esultat interm´ediaire) ; si oui, on lib`ere certains des registres utilis´es, en stockant leur valeur dans la pile avant l’´evaluation de l’expression, puis en les rechargeant depuis la pile une fois l’expression ´evalu´ee. Fichier compil.ml let dernier_registre = 24;; let rec besoins env = function | Constante c -> 0 | Variable nom -> 0 | Application(fonc, args) -> dernier_registre | Op_unaire(op, arg) -> besoins env arg | Op_binaire(op, arg1, arg2) -> besoins_op_binaire env arg1 arg2 | Acc`es_tableau(arg1, arg2) -> besoins_op_binaire env arg1 arg2 and besoins_op_binaire env arg1 arg2 = Compilation 295 let b1 = besoins env arg1 and b2 = besoins env arg2 in if b1 < b2 && (sans_interf´erences env arg1 || sans_interf´erences env arg2) then max b2 (b1 + 1) else max b1 (b2 + 1);; L’´evaluation des expressions peut utiliser tous les registres dont les num´eros sont compris entre 1 et dernier_registre pour stocker des r´esultats interm´ediaires. Les registres au-dessus de dernier_registre sont r´eserv´es pour d’autres usages (temporaires, pointeur de pile). La constante dernier_registre est le nombre de registres du processeur que nous pouvons utiliser pour l’´evaluation des expressions. On suppose que l’application d’une fonction ne pr´eserve aucun de ces registres : la fonction appel´ee peut les utiliser librement, pour ´evaluer des expressions arbitrairement compliqu´ees, sans obligation de sauvegarder leur contenu. Une application de fonction a donc besoin de tous les registres. Pour une op´eration unaire comme not e, si on peut ´evaluer e avec n registres libres, on peut aussi ´evaluer not e avec n registres libres : il suffit d’´evaluer e dans un certain registre r, puis de faire l’instruction seq r 0, r, r qui ne n´ecessite pas de registre temporaire suppl´ementaire. Le cas des op´erations binaires est plus subtil. Pour ´evaluer e1 +e2, par exemple, on peut commencer par ´evaluer e1, puis e2, puis faire add sur les deux r´esultats. Mais il faut que la valeur de e1 ne soit pas d´etruite pendant l’´evaluation de e2. Pour ce faire, on a besoin d’un registre libre suppl´ementaire pendant l’´evaluation de e2, registre dans lequel on conservera la valeur de e1. Les besoins de l’expression e1 + e2 ainsi compil´ee sont donc le plus grand de besoins e1 et de 1 + besoins e2. Si les expressions e1 et e2 sont susceptibles d’interf´erer, nous sommes oblig´es, pour respecter la s´emantique de mini-Pascal, d’´evaluer e1 d’abord, puis e2. En revanche, si l’une des expressions est garantie sans interf´erences, nous pouvons ´evaluer e2 avant e1 sans changer le comportement du programme. Si on ´evalue d’abord e2 puis e1, le nombre de registres utilis´es est le plus grand de besoins e2 et de 1 + besoins e1. On choisit donc celle des deux strat´egies qui utilise le moins de registres. On montre facilement que la strat´egie « e2 d’abord » utilise moins de registres que la strat´egie « e1 d’abord » si et seulement si besoins e2 est plus grand que besoins e1. La strat´egie correspond donc `a ´evaluer en premier celle des deux sous-expressions qui a les plus grands besoins en registres. (Cette m´ethode est connue dans la litt´erature sous le nom d’algorithme d’Ershov.) Compilation d’une expression Nous pouvons maintenant attaquer la fonction de compilation d’une expression. Cette fonction prend en arguments un environnement, une expression et un registre de destination et affiche sur la sortie standard le code machine qui calcule la valeur de cette expression et met le r´esultat dans le registre demand´e. La plus grande partie de la fonction compile_expr se passe de commentaire. Nous utilisons toute la puissance du filtrage pour reconnaˆıtre certains cas particuliers qui correspondent directement `a 296 Compilation de mini-Pascal des modes d’adressage de la machine, comme par exemple l’addition d’une constante ou le chargement avec un d´ecalage constant. Fichier compil.ml let instr_pour_op = function | "+" -> "add" | "-" -> "sub" | "*" -> "mult" | "/" -> "div" | "=" -> "seq" | "<>" -> "sne" | "<" -> "slt" | ">" -> "sgt" | "<=" -> "sle" | ">=" -> "sge" | "and" -> "and" | "or" -> "or";; let rec compile_expr env expr reg = match expr with | Constante cst -> printf "add r 0, %d, r %d\n" (val_const cst) reg | Variable nom -> let var = cherche_variable nom env in begin match var.emplacement with | Global_indirect n -> printf "load r 0, %d, r %d # %s \n" n reg nom | Global_direct n -> printf "add r 0, %d, r %d # %s \n" n reg nom | Local_indirect n -> printf "load sp, %d, r %d # %s \n" (!profondeur_pile - n) reg nom | Local_direct n -> printf "add sp, %d, r %d # %s \n" (!profondeur_pile - n) reg nom end | Application(fonc, arguments) -> let nbr_args = list_length arguments in r´eserve_pile nbr_args; let position = ref 0 in do_list (function arg -> compile_expr env arg 1; printf "store sp, %d, r 1\n" !position; position := !position + taille_du_mot) arguments; printf "jmp F%s, ra\n" fonc; lib`ere_pile nbr_args; if reg <> 1 then printf "add r 1, r 0, r %d\n" reg | Op_unaire(op, arg) -> compile_expr env arg reg; begin match op with | "-" -> printf "sub r 0, r %d, r %d\n" reg reg | "not" -> printf "seq r 0, r %d, r %d\n" reg reg end | Op_binaire(op, arg1, Constante cst2) -> compile_expr env arg1 reg; printf "%s r %d, %d, r %d\n" (instr_pour_op op) reg (val_const cst2) reg Compilation 297 | Op_binaire(("+" | "*" | "=" | "<>" | "and" | "or") as op, Constante cst1, arg2) -> compile_expr env arg2 reg; printf "%s r %d, %d, r %d\n" (instr_pour_op op) reg (val_const cst1) reg | Op_binaire(op, arg1, arg2) -> let (reg1, reg2) = compile_arguments env arg1 arg2 reg in printf "%s r %d, r %d, r %d\n" (instr_pour_op op) reg1 reg2 reg | Acc`es_tableau(arg1, Constante cst) -> let (inf, sup, type_´el´ements) = type_de_tableau env arg1 in compile_expr env arg1 reg; begin match type_´el´ements with | Integer | Boolean -> printf "load r %d, %d, r %d\n" reg ((val_const cst - inf) * taille_du_mot) reg | Array(_, _, _) -> let taille = taille_du_type type_´el´ements in printf "add r %d, %d, r %d\n" reg ((val_const cst - inf) * taille) reg end | Acc`es_tableau(arg1, arg2) -> let (inf, sup, type_´el´ements) = type_de_tableau env arg1 in let (reg1, reg2) = compile_arguments env arg1 arg2 reg in if inf <> 0 then printf "sub r %d, %d, r %d\n" reg2 inf reg2; begin match type_´el´ements with | Integer | Boolean -> printf "mult r %d, %d, r %d\n" reg2 taille_du_mot reg2; printf "load r %d, r %d, r %d\n" reg1 reg2 reg | Array(_, _, typ) -> let taille = taille_du_type type_´el´ements in printf "mult r %d, %d, r %d\n" reg2 taille reg2; printf "add r %d, r %d, r %d\n" reg1 reg2 reg end and compile_arguments env arg1 arg2 reg_libre = let b1 = besoins env arg1 and b2 = besoins env arg2 in if b1 < b2 && (sans_interf´erences env arg1 || sans_interf´erences env arg2) then begin let (reg2, reg1) = compile_arguments env arg2 arg1 reg_libre in (reg1, reg2) end else begin compile_expr env arg1 reg_libre; if b2 < dernier_registre - reg_libre then begin compile_expr env arg2 (reg_libre + 1); (reg_libre, reg_libre + 1) end else begin r´eserve_pile 1; printf "store sp, 0, r %d\n" reg_libre; compile_expr env arg2 reg_libre; printf "load sp, 0, r 29\n"; 298 Compilation de mini-Pascal lib`ere_pile 1; (29, reg_libre) end end;; La fonction compile_arguments impl´emente la strat´egie d’´evaluation pr´esent´ee plus haut. Elle ´evalue deux expressions en s´equence et renvoie les num´eros de deux registres contenant leur valeur. Le dernier argument, reg_libre, est le num´ero du premier registre libre. Tous les registres inf´erieurs `a reg_libre seront pr´eserv´es ; tous les autres peuvent ˆetre utilis´es librement. Si on peut permuter sans risques les deux ´evaluations et si cela peut r´eduire le nombre de registres utilis´es (c’est-`a-dire si arg2 a de plus grands besoins que arg1), la fonction compile_arguments se rappelle apr`es avoir permut´e arg2 et arg1. Sinon, elle commence par ´emettre le code ´evaluant arg1 dans reg_libre. Ensuite, reg_libre n’est plus libre (il contient la valeur de arg1). Donc, de deux choses l’une : ou bien on peut ´evaluer arg2 avec les registres restants, auquel cas la fonction ´emet le code qui ´evalue arg2 avec reg_libre + 1 comme registre de destination ; ou bien on n’a plus assez de registres libres, auquel cas la fonction sauvegarde la valeur de arg1 sur la pile (ce qui lib`ere le registre reg_libre), ´emet le code qui ´evalue arg2 dans reg_libre et recharge la valeur de arg1 dans un registre temporaire (le registre 29). Compilation des instructions On passe maintenant `a la compilation d’une instruction. La plupart des cas sont similaires `a ceux de la compilation d’une expression. Fichier compil.ml let compteur_d’´etiquettes = ref 0;; let nouvelle_´etiq () = incr compteur_d’´etiquettes; !compteur_d’´etiquettes;; let rec compile_instr env = function | Affectation_var(nom_var, Constante(Enti`ere 0 | Bool´eenne false)) -> affecte_var env nom_var 0 | Affectation_var(nom_var, expr) -> compile_expr env expr 1; affecte_var env nom_var 1 | Affectation_tableau(expr1, Constante cst2, expr3) -> let (inf, sup, type_´el´ements) = type_de_tableau env expr1 in let (reg3, reg1) = compile_arguments env expr3 expr1 1 in printf "store r %d, %d, r %d\n" reg1 ((val_const cst2 - inf) * taille_du_mot) reg3 | Affectation_tableau(expr1, expr2, expr3) -> let (inf, sup, type_´el´ements) = type_de_tableau env expr1 in compile_expr env expr3 1; let (reg1, reg2) = compile_arguments env expr1 expr2 2 in if inf <> 0 then printf "sub r %d, %d, r %d\n" reg2 inf reg2; printf "mult r %d, %d, r %d\n" reg2 taille_du_mot reg2; Compilation 299 printf "store r %d, r %d, r %d\n" reg1 reg2 1 | Appel(proc, arguments) -> let nbr_args = list_length arguments in r´eserve_pile nbr_args; let position = ref 0 in do_list (function arg -> compile_expr env arg 1; printf "store sp, %d, r 1\n" !position; position := !position + taille_du_mot) arguments; printf "jmp P%s, ra\n" proc; lib`ere_pile nbr_args | If(condition, branche_oui, Bloc []) -> let ´etiq_fin = nouvelle_´etiq () in compile_expr env condition 1; printf "braz r 1, L%d\n" ´etiq_fin; compile_instr env branche_oui; printf "L%d:\n" ´etiq_fin | If(condition, Bloc [], branche_non) -> let ´etiq_fin = nouvelle_´etiq () in compile_expr env condition 1; printf "branz r 1, L%d\n" ´etiq_fin; compile_instr env branche_non; printf "L%d:\n" ´etiq_fin | If(Op_unaire("not", condition), branche_oui, branche_non) -> compile_instr env (If(condition, branche_non, branche_oui)) | If(condition, branche_oui, branche_non) -> let ´etiq_non = nouvelle_´etiq () and ´etiq_fin = nouvelle_´etiq () in compile_expr env condition 1; printf "braz r 1, L%d\n" ´etiq_non; compile_instr env branche_oui; printf "braz r 0, L%d\n" ´etiq_fin; printf "L%d:\n" ´etiq_non; compile_instr env branche_non; printf "L%d:\n" ´etiq_fin | While(condition, corps) -> let ´etiq_corps = nouvelle_´etiq () and ´etiq_test = nouvelle_´etiq () in printf "braz r 0, L%d\n" ´etiq_test; printf "L%d:\n" ´etiq_corps; compile_instr env corps; printf "L%d:\n" ´etiq_test; compile_expr env condition 1; printf "branz r 1, L%d\n" ´etiq_corps | Write expr -> compile_expr env expr 1; printf "write\n" | Read nom_var -> printf "read\n"; affecte_var env nom_var 1 300 Compilation de mini-Pascal | Bloc liste_instr -> do_list (compile_instr env) liste_instr and affecte_var env nom reg = let var = cherche_variable nom env in match var.emplacement with | Global_indirect n -> printf "store r 0, %d, r %d # %s \n" n reg nom | Local_indirect n -> printf "store sp, %d, r %d # %s \n" (!profondeur_pile - n) reg nom;; Pour l’instruction if e then i1 else i2, le code produit a la forme suivante : code pour ´evaluer e dans le registre r 1 braz r 1, Ln (branche si r 1 est false) code pour ex´ecuter i1 braz r 0, Lm (branche toujours) Ln: code pour ex´ecuter i2 Lm: suite du programme Les ´etiquettes Ln et Lm sont de nouvelles ´etiquettes produites par la fonction nouvelle_´etiq. Dans le cas o`u i2 est l’instruction vide (cas d’un if sans partie else), on supprime le branchement `a Lm, qui ne sert `a rien dans ce cas. On proc`ede de mˆeme pour l’instruction while e do i . Le test est plac´e `a la fin du corps de la boucle, pour ne faire qu’un saut par tour de boucle au lieu de deux : braz r 0, Lm (branche toujours) Ln: code pour ex´ecuter i Lm: code pour ´evaluer e dans le registre r 1 branz r 1, Ln (branche si r 1 est true) suite du programme Compilation des fonctions et des proc´edures La compilation d’une fonction ou d’une proc´edure se fait en deux parties : il faut d’une part attribuer des emplacements aux param`etres et aux variables locales et construire l’environnement correspondant ; d’autre part, il faut ´emettre le code qui construit le bloc d’activation sur la pile. La seule subtilit´e est dans l’attribution des emplacements : une variable locale de type tableau a un emplacement de type Local_direct, puisque le tableau est allou´e `a plat dans la pile ; en revanche, un param`etre de type tableau a un emplacement de type Local_indirect, puisque c’est un pointeur vers le tableau pass´e en argument qui est empil´e (passage par r´ef´erence) et non pas le tableau lui-mˆeme (passage par valeur). Fichier compil.ml let alloue_variable_locale (nom, typ) env = profondeur_pile := !profondeur_pile + taille_du_type typ; let emplacement = match typ with | Integer | Boolean -> Compilation 301 Local_indirect(!profondeur_pile) | Array(_, _, _) -> Local_direct(!profondeur_pile) in ajoute_variable nom {typ=typ; emplacement=emplacement} env;; let alloue_param`etres liste_des_param`etres environnement = let prof = ref 0 in let env = ref environnement in do_list (function (nom,typ) -> env := ajoute_variable nom {typ=typ; emplacement = Local_indirect !prof} !env; prof := !prof - taille_du_mot) liste_des_param`etres; !env;; let compile_proc´edure env (nom, d´ecl) = let env1 = alloue_param`etres d´ecl.proc_param`etres env in profondeur_pile := taille_du_mot; let env2 = list_it alloue_variable_locale d´ecl.proc_variables env1 in printf "P%s:\n" nom; printf "sub sp, %d, sp\n" !profondeur_pile; printf "store sp, %d, ra\n" (!profondeur_pile - taille_du_mot); compile_instr env2 d´ecl.proc_corps; printf "load sp, %d, ra\n" (!profondeur_pile - taille_du_mot); printf "add sp, %d, sp\n" !profondeur_pile; printf "jmp ra, r 0\n";; let compile_fonction env (nom, d´ecl) = let env1 = alloue_param`etres d´ecl.fonc_param`etres env in profondeur_pile := taille_du_mot; let env2 = list_it alloue_variable_locale d´ecl.fonc_variables env1 in let env3 = alloue_variable_locale (nom, d´ecl.fonc_type_r´esultat) env2 in printf "F%s:\n" nom; printf "sub sp, %d, sp\n" !profondeur_pile; printf "store sp, %d, ra\n" (!profondeur_pile - taille_du_mot); compile_instr env3 d´ecl.fonc_corps; printf "load sp, 0, r 1\n"; printf "load sp, %d, ra\n" (!profondeur_pile - taille_du_mot); printf "add sp, %d, sp\n" !profondeur_pile; printf "jmp ra, r 0\n";; 302 Compilation de mini-Pascal Compilation d’un programme Tout est prˆet pour compiler un programme complet. Nous commen¸cons par attribuer des adresses aux variables globales, obtenant ainsi l’environnement global de compilation, puis compilons successivement le corps du programme, les proc´edures et les fonctions dans cet environnement. Fichier compil.ml let adresse_donn´ee = ref 0;; let alloue_variable_globale (nom, typ) env = let emplacement = match typ with | Integer | Boolean -> Global_indirect(!adresse_donn´ee) | Array(_, _, _) -> Global_direct(!adresse_donn´ee) in adresse_donn´ee := !adresse_donn´ee + taille_du_type typ; ajoute_variable nom {typ=typ; emplacement=emplacement} env;; let compile_programme prog = adresse_donn´ee := 0; let env_global = list_it alloue_variable_globale prog.prog_variables (environnement_initial prog.prog_proc´edures prog.prog_fonctions) in compile_instr env_global prog.prog_corps; printf "stop\n"; do_list (compile_proc´edure env_global) prog.prog_proc´edures; do_list (compile_fonction env_global) prog.prog_fonctions;; Le compilateur complet Pour terminer, voici le programme principal qui combine l’analyseur syntaxique, le v´erificateur de types et le compilateur. Fichier cpascal.ml #open "syntaxe";; let compile_fichier nom = try let canal = open_in sys__command_line.(1) in try let prog = lire_programme (stream_of_channel canal) in close_in canal; typage__type_programme prog; compil__compile_programme prog with Parse_error | Parse_failure -> prerr_string "Erreur de syntaxe aux alentours \ du caract`ere num´ero "; prerr_int (pos_in canal); prerr_endline "" | typage__Erreur_typage err -> typage__affiche_erreur err Compilation 303 with sys__Sys_error message -> prerr_string "Erreur du syst`eme: "; prerr_endline message;; if sys__interactive then () else begin compile_fichier sys__command_line.(1); exit 0 end;; Mise en pratique Pour compiler le tout : $ camlc -c lexuniv.mli $ camlc -c lexuniv.ml $ camlc -c syntaxe.mli $ camlc -c syntaxe.ml $ camlc -c envir.mli $ camlc -c envir.ml $ camlc -c typage.mli $ camlc -c typage.ml $ camlc -c compil.mli $ camlc -c compil.ml $ camlc -c cpascal.ml $ camlc -o cpascal lexuniv.zo syntaxe.zo envir.zo \ typage.zo compil.zo cpascal.zo Lan¸cons le compilateur sur le fichier fib1.pas donn´e en exemple page 277, par camlrun cpascal fib1.pas depuis l’interpr`ete de commandes, ou par cpascal__compile__fichier "fib1.pas" depuis le syst`eme interactif. Nous obtenons le code suivant (la pr´esentation a ´et´e l´eg`erement modifi´ee pour ˆetre plus lisible). read store r 0, 0, r 1 # n sub sp, 4, sp load r 0, 0, r 1 # n store sp, 0, r 1 jmp Ffib, ra add sp, 4, sp write stop Ffib: sub sp, 8, sp store sp, 4, ra load sp, 8, r 1 # n slt r 1, 2, r 1 braz r 1, L1 add r 0, 1, r 1 store sp, 0, r 1 # fib braz r 0, L2 L1: sub sp, 4, sp load sp, 12, r 1 # n sub r 1, 1, r 1 store sp, 0, r 1 jmp Ffib, ra add sp, 4, sp sub sp, 4, sp store sp, 0, r 1 sub sp, 4, sp load sp, 16, r 1 # n sub r 1, 2, r 1 store sp, 0, r 1 jmp Ffib, ra add sp, 4, sp load sp, 0, r 29 add sp, 4, sp add r 29, r 1, r 1 store sp, 0, r 1 # fib L2: load sp, 0, r 1 load sp, 4, ra add sp, 8, sp jmp ra, r 0 Quoique loin d’ˆetre optimal, ce code n’est pas de trop mauvaise facture. L’inefficacit´e la plus grossi`ere est la s´equence add sp, 4, sp; sub sp, 4, sp au milieu de la 304 Compilation de mini-Pascal fonction fib, qui pourrait avantageusement ˆetre supprim´ee. De mani`ere plus g´en´erale, il vaudrait mieux calculer `a l’avance le nombre d’emplacements de pile n´ecessaires pour les temporaires et les allouer une fois pour toutes au d´ebut de la fonction, plutˆot que d’incr´ementer et de d´ecr´ementer le pointeur de pile `a chaque fois qu’on a besoin d’un temporaire. 15.4 Pour aller plus loin Le compilateur Pascal pr´esent´e dans ce chapitre se prˆete `a de nombreuses extensions. Une premi`ere direction est d’enrichir le langage : pointeurs, nombres flottants, fonctions et proc´edures locales, . . . En particulier, les fonctions et proc´edures locales posent des probl`emes de compilation int´eressants. Lorsque le langage interdit aux fonctions locales d’ˆetre renvoy´ees en r´esultats par d’autres fonctions, comme c’est le cas en Pascal, un chaˆınage des blocs d’activation dans la pile suffit. En revanche, dans les langages fonctionnels comme Caml o`u les fonctions sont des valeurs « de premi`ere classe », une pile ne suffit plus pour repr´esenter les environnements d’´evaluation des fonctions et il est n´ecessaire de mod´eliser les fonctions par des structures allou´ees dynamiquement : les fermetures (closures, en anglais). Nous reviendrons sur cette notion au chapitre 17. Une deuxi`eme direction est d’am´eliorer la qualit´e du code produit par le compilateur. En particulier, il faudrait essayer de stocker les variables locales autant que possible dans les registres et non pas dans la pile. D´etecter les variables locales qui se prˆetent `a ce traitement et leur attribuer des registres de mani`ere coh´erente est un probl`eme difficile. Une troisi`eme direction est d’am´eliorer la structure interne du compilateur. Nous avons vu que, pour afficher correctement les erreurs de typage, il faudrait annoter les noeuds de l’arbre de syntaxe abstraite par des num´eros de ligne et des positions dans le code source. D’autres informations devraient elles aussi figurer en annotations sur l’arbre de syntaxe abstraite, comme le type attribu´e `a chaque noeud par la phase de typage, ainsi que les besoins en registres de chaque expression. Cela ´eviterait de recalculer de nombreuses fois ces informations lors de la compilation. Bibliographie Parmi les nombreux ouvrages publi´es sur les compilateurs, nous recommandons Compilateurs : principes, techniques et outils, de Aho, Sethi et Ullman (Inter´Editions) et Modern compiler implementation in ML, d’Appel (Cambridge University Press). 16 Recherche de motifs dans un texte Un programme pour les ´etoiles. echercher les occurrences d’un motif dans un texte est une op´eration cruciale dans de nombreux outils informatiques : traitement de texte, ´editeurs, navigateurs Web, etc. Nous impl´ementons ici un outil qui affiche toutes les lignes d’un fichier contenant un motif donn´e, dans le style de la commande grep d’Unix. Pour ce faire, nous introduisons les notions d’automates et d’expressions rationnelles, qui sont un des fondements de l’informatique. Nous montrons comment manipuler en Caml des graphes et plus g´en´eralement des structures de donn´ees qui contiennent des cycles. 16.1 Les motifs Dans le cas le plus simple, le motif que l’on recherche dans un fichier est une suite de caract`eres pr´ecise. Par exemple, en Unix, la commande grep xop dictionnaire affiche toutes les lignes du fichier dictionnaire qui contiennent la chaˆıne xop. Pour plus de souplesse dans la recherche, on autorise des « jokers » dans la chaˆıne `a chercher. Ainsi, grep c.r dictionnaire affiche toutes les lignes contenant un c suivi d’une lettre quelconque puis d’un r ; de mˆeme grep c.*r affiche toutes les lignes contenant un c puis un r, s´epar´es par un nombre quelconques de lettres. Nous utiliserons une famille encore plus g´en´erale de motifs connus sous le nom d’expressions rationnelles (en anglais regular expressions). Une expression rationnelle est ou bien : • un caract`ere c • l’expression vide, not´ee " • une alternative e1 | e2, o`u e1 et e2 sont elles-mˆemes deux expressions rationnelles • une s´equence e1e2, o`u e1 et e2 sont elles-mˆemes deux expressions rationnelles • une r´ep´etition e∗, o`u e est une expression rationnelle. 306 Recherche de motifs dans un texte Pour chaque expression rationnelle, on d´efinit les chaˆınes de caract`eres reconnues par cette expression rationnelle. • L’expression c, o`u c est un caract`ere, reconnaˆıt la chaˆıne `a un seul caract`ere c, et rien d’autre. • L’expression " reconnaˆıt la chaˆıne vide et rien d’autre. • L’expression e1 | e2 reconnaˆıt les chaˆınes qui sont reconnues par e1 ou par e2. • L’expression e1e2 reconnaˆıt les chaˆınes compos´ees d’une chaˆıne reconnue par e1 suivie d’une chaˆıne reconnue par e2. • L’expression e∗ reconnaˆıt les chaˆınes compos´ees de z´ero, une ou plusieurs chaˆınes toutes reconnues par e. Par exemple, l’expression cal(i|y)(ph|f)e reconnaˆıt quatre orthographes envisageables pour le mot calife. De mˆeme, l’expression (−|+|") (0|1|2|3|4|5|6|7|8|9) (0|1|2|3|4|5|6|7|8|9)∗ reconnaˆıt toutes les repr´esentations d´ecimales de nombres entiers, ´eventuellement pr´ec´ed´ees d’un signe. 16.2 Syntaxe abstraite et syntaxe concr`ete des motifs Commen¸cons par construire un module expr d´efinissant la repr´esentation interne (syntaxe abstraite) des expressions rationnelles, ainsi qu’une fonction pour lire une repr´esentation textuelle (syntaxe concr`ete) d’une expression rationnelle. Voici l’interface de ce module : Fichier expr.mli type expr = | Epsilon | Caract`eres of char list | Alternative of expr * expr | S´equence of expr * expr | R´ep´etition of expr;; value lire : char stream -> expr;; La syntaxe abstraite (le type concret expr) suit de tr`es pr`es la d´efinition des expressions rationnelles donn´ee ci-dessus. La seule diff´erence est que, pour des raisons d’efficacit´e, le constructeur Caract`eres prend en argument non pas un seul caract`ere, mais une liste de caract`eres. L’expression Caract`eres[c1; . . . ; cn] repr´esente l’alternative c1 | . . . | cn. La syntaxe concr`ete des expressions rationnelles ressemble `a celle employ´ee par grep. Elle introduit un certain nombre de constructions syntaxiques d´eriv´ees des constructions de base (alternative, s´equence, r´ep´etition). • Le point . repr´esente n’importe quel caract`ere, c’est-`a-dire l’alternative entre tous les caract`eres. Syntaxe abstraite et syntaxe concr`ete des motifs 307 • Un caract`ere non sp´ecial repr´esente l’expression mono-caract`ere correspondante. La barre oblique invers´ee (backslash) sert d’´echappement pour les caract`eres sp´eciaux : \ suivi de n’importe quel autre caract`ere repr´esente ce caract`ere. En particulier, \\ est le caract`ere \. • Les crochets [ . . . ] repr´esentent des ensembles de caract`eres. Par exemple, [aeiou] repr´esente a|e|i|o|u. Le tiret - d´enote des intervalles : [0-9] repr´esente tous les chiffres, [A-Za-z] toutes les lettres. On prend le compl´ementaire d’un ensemble en mettant un chapeau ^ juste apr`es le crochet : [^0-9] repr´esente tout caract`ere qui n’est pas un chiffre. • Les combinaisons d’expressions rationnelles sont not´ees comme suit : Combinaison Notation Exemple Signification Alternative | infixe le|la|les le, ou la, ou les S´equence concat´enation x[0-9] x puis un chiffre R´ep´etition * postfixe [0-9]* z´ero, un ou plusieurs chiffres R´ep´etition stricte + postfixe [0-9]+ un ou plusieurs chiffres Option ? postfixe [+-]? un signe plus, un signe moins, ou rien L’alternative a la priorit´e la plus faible, puis la s´equence, puis les r´ep´etitions. Pour passer outre ces priorit´es, on dispose des parenth`eses ( . . . ). • Un signe chapeau ^ en d´ebut d’expression rationnelle signifie que le mot reconnu doit apparaˆıtre en d´ebut de ligne. Un signe dollar $ en fin d’expression rationnelle signifie que le mot reconnu doit se trouver en fin de ligne. Par d´efaut, le mot reconnu se situe n’importe o`u dans la ligne. Notation Reconnaˆıt . . . Codage ^e$ les lignes reconnues par e e ^e les lignes commen¸cant par un mot reconnu par e e.* e$ les lignes finissant par un mot reconnu par e .*e e les lignes contenant un mot reconnu par e .*e.* Voici maintenant l’impl´ementation du module expr, qui fournit l’analyseur pour la syntaxe qu’on vient de d´ecrire. Fichier expr.ml let intervalle c1 c2 = let rec interv n1 n2 = if n1 > n2 then [] else char_of_int n1 :: interv (n1 + 1) n2 in interv (int_of_char c1) (int_of_char c2);; let tous_car = intervalle ‘\000‘ ‘\255‘;; 308 Recherche de motifs dans un texte La fonction intervalle construit la liste de tous les caract`eres entre les deux caract` eres donn´es. Elle sert pour l’expansion des classes de caract`eres. La liste tous_car est la liste des 256 caract`eres du code ASCII. Elle sert pour l’expansion de la construction « . » en la classe de tous les caract`eres. L’essentiel de l’analyse syntaxique est effectu´e par le groupe de fonctions mutuellement r´ecursives ci-dessous. (La syntaxe d’entr´ee est si primitive que nous n’avons pas besoin d’une phase pr´ealable d’analyse lexicale.) Le d´ecoupage en plusieurs fonctions interm´ediaires assure en particulier que les priorit´es entre op´erateurs sont respect´ees. Fichier expr.ml let rec lire_expr = function | [< lire_s´eq r1; (lire_alternative r1) r2 >] -> r2 and lire_alternative r1 = function | [< ’‘|‘; lire_expr r2 >] -> Alternative(r1,r2) | [< >] -> r1 and lire_s´eq = function | [< lire_r´ep´et r1; (lire_fin_s´eq r1) r2 >] -> r2 and lire_fin_s´eq r1 = function | [< lire_s´eq r2 >] -> S´equence(r1,r2) | [< >] -> r1 and lire_r´ep´et = function | [< lire_simple r1; (lire_fin_r´ep´et r1) r2 >] -> r2 and lire_fin_r´ep´et r1 = function | [< ’‘*‘ >] -> R´ep´etition r1 | [< ’‘+‘ >] -> S´equence(r1, R´ep´etition r1) | [< ’‘?‘ >] -> Alternative(r1, Epsilon) | [< >] -> r1 and lire_simple = function | [< ’‘.‘ >] -> Caract`eres tous_car | [< ’‘[‘; lire_classe cl >] -> Caract`eres cl | [< ’‘(‘; lire_expr r; ’‘)‘ >] -> r | [< ’‘\\‘; ’c >] -> Caract`eres [c] | [< (stream_check (function c -> c <> ‘|‘ && c <> ‘)‘ && c <> ‘$‘)) c >] -> Caract`eres [c] and lire_classe = function | [< ’‘^‘; lire_ensemble cl >] -> subtract tous_car cl | [< lire_ensemble cl >] -> cl and lire_ensemble = function | [< ’‘]‘ >] -> [] | [< lire_car c1; (lire_intervalle c1) c2 >] -> c2 and lire_intervalle c1 = function Les automates 309 | [< ’‘-‘; lire_car c2; lire_ensemble reste >] -> union (intervalle c1 c2) reste | [< lire_ensemble reste >] -> union [c1] reste and lire_car = function | [< ’‘\\‘; ’c >] -> c | [< ’c >] -> c;; Nous avons dˆu introduire les fonctions interm´ediaires lire_alternative, lire_fin_s´eq, etc., pour tenir compte du caract`ere enti`erement d´eterministe du filtrage sur les flux. Pour lire_expr par exemple, il aurait ´et´e plus naturel d’´ecrire : let rec lire_expr = function | [< lire_s´eq r1; ’‘|‘; lire_expr r2 >] -> Alternative(r1,r2) | [< lire_s´eq r1 >] -> r1 Cette ´ecriture ne donne pas le r´esultat attendu : si lire_s´eq reconnaˆıt le d´ebut du flux, on s’engage de mani`ere d´efinitive dans le premier cas du filtrage. Si le prochain caract`ere du flux n’est pas une barre verticale, le syst`eme d´eclenche une erreur de syntaxe, mais ne se rabat pas sur le deuxi`eme cas du filtrage. Il faut donc appliquer aux motifs des fonctions d’analyse la technique connue sous le nom de « factorisation `a gauche » : la fonction lire_expr commence par reconnaˆıtre le pr´efixe commun aux deux cas, c’est-`a-dire lire_s´eq, puis appelle la fonction d’analyse auxiliaire lire_alternative pour tester la pr´esence de la barre verticale. S’il y en a une, le premier cas de lire_alternative est s´electionn´e, et appelle r´ecursivement lire_expr pour lire l’expression qui suit. Sinon, lire_alternative ne lit rien et lire_expr renvoie simplement l’expression lue par lire_s´eq. Fichier expr.ml let lire = function | [< (function [< ’‘^‘ >] -> true | [< >] -> false) chapeau; lire_expr r; (function [< ’‘$‘ >] -> true | [< >] -> false) dollar >] -> let r1 = if dollar then r else S´equence(r, R´ep´etition(Caract`eres tous_car)) in if chapeau then r1 else S´equence(R´ep´etition(Caract`eres tous_car), r1);; La fonction lire, point d’entr´ee du module, lit une expression rationnelle ´eventuellement pr´ec´ed´ee d’un caract`ere ^ ou suivie d’un caract`ere $. Remarquez que, depuis l’int´erieur d’un motif de flux, il est possible d’appeler des fonctions d’analyse anonymes (non nomm´ees), introduites par function. 16.3 Les automates Pour programmer la commande grep, il faut savoir d´eterminer si une expression rationnelle reconnaˆıt une chaˆıne de caract`eres. La traduction na¨ıve de la d´efinition des chaˆınes reconnues par une expression rationnelle m`ene `a un algorithme par essais et erreurs qui est tr`es inefficace dans les cas d´efavorables. Intuitivement, pour reconnaˆıtre 310 Recherche de motifs dans un texte une expression de la forme .*e.*, il faut essayer de reconnaˆıtre e `a toutes les positions possibles dans la chaˆıne. Si plusieurs expressions de ce type se suivent, comme dans .*e1.*e2 . . . en.*, le nombre de combinaisons `a essayer devient tr`es grand. La mani`ere efficace de d´eterminer si une expression rationnelle reconnaˆıt une chaˆıne de caract`eres est de transformer d’abord l’expression rationnelle en un automate qui reconnaˆıt les mˆemes mots, puis d’ex´ecuter l’automate sur ladite chaˆıne de caract`eres. Intuitivement, un automate est une machine tr`es simplifi´ee, sp´ecialis´ee dans la reconnaissance des mots. Elle se compose d’un certain nombre d’´etats (repr´esent´es par des cercles) reli´es entre eux par des transitions (repr´esent´ees par des fl`eches). Les transitions sont ´etiquet´ees ou bien par une lettre, ou bien par le mot vide ". a " b c La r`egle du jeu est la suivante : `a partir d’un ´etat, on a le droit de passer dans un autre ´etat soit parce qu’il existe une transition marqu´ee " de l’´etat de d´epart vers l’´etat d’arriv´ee, soit parce qu’il existe une transition marqu´ee x (o`u x est le prochain caract`ere de la chaˆıne `a reconnaˆıtre) de l’´etat de d´epart vers l’´etat d’arriv´ee. Par exemple, dans l’automate ci-dessus, on peut passer de l’´etat de gauche `a l’´etat de droite si la chaˆıne `a reconnaˆıtre commence par ab, ac, b, ou c. Dans un automate, certains ´etats sont marqu´es comme ´etats terminaux. Un des ´etats est marqu´e comme ´etat initial. (Dans les dessins, l’´etat initial est signal´e par une petite fl`eche entrante du cˆot´e gauche ; les ´etats terminaux sont en trait plus ´epais.) l a l e s - " 0 1 0 1 Le jeu consiste `a essayer de trouver un chemin qui part de l’´etat initial et aboutit sur un ´etat terminal, apr`es avoir lu tous les caract`eres de la chaˆıne donn´ee en entr´ee. Si un tel chemin existe, on dit que l’automate reconnaˆıt la chaˆıne. Par exemple, l’automate ci-dessus `a gauche reconnaˆıt les mots le, la, les, et rien d’autre. L’automate ci-dessus `a droite reconnaˆıt les nombres ´ecrits en base deux, c’est-`a-dire les mˆemes mots que l’expression rationnelle -?[01]+. 16.4 Des expressions rationnelles aux automates Expressions rationnelles et automates sont reli´es de mani`ere tr`es ´etroite : `a toute expression rationnelle correspond un automate qui reconnaˆıt exactement les mˆemes mots que l’expression de d´epart. Les automates peuvent donc ˆetre vus comme des formes compil´ees d’expressions rationnelles. Nous allons maintenant programmer une fonction qui transforme une expression rationnelle en automate. Le module correspondant s’appelle auto ; voici son interface. Des expressions rationnelles aux automates 311 Fichier auto.mli #open "expr";; type ´etat = { mutable transitions : (char * ´etat) list; mutable epsilon_transitions : ´etat list; mutable terminal : bool; num´ero : int };; value expr_vers_automate : expr -> ´etat;; Un ´etat de l’automate est repr´esent´e par un enregistrement `a quatre champs. Le champ terminal indique si l’´etat est terminal ou non. Les champs transitions et epsilon_transitions contiennent la liste des fl`eches sortant de l’´etat, avec pour chaque fl`eche l’´etat auquel elle m`ene. Le champ num´ero sert `a identifier les ´etats de mani`ere unique : deux ´etats diff´erents portent des num´eros diff´erents. L’automate lui-mˆeme est repr´esent´e par son ´etat initial. Les autres ´etats de l’automate « pendent » sous l’´etat initial : ils sont accessibles en descendant dans les champs transitions et epsilon_transitions. L’impl´ementation du module auto comporte deux parties : premi`erement, quelques petites fonctions de manipulation des ´etats ; deuxi`emement, la fonction de compilation d’une expression rationnelle en automate. Fichier auto.ml #open "expr";; let compteur_d’´etats = ref 0;; let nouvel_´etat () = incr compteur_d’´etats; { transitions = []; epsilon_transitions = []; terminal = false; num´ero = !compteur_d’´etats };; let ajoute_trans n1 c n2 = n1.transitions <- (c, n2) :: n1.transitions;; let ajoute_eps_trans n1 n2 = n1.epsilon_transitions <- n2 :: n1.epsilon_transitions;; type automate_de_thompson = { initial : ´etat; final : ´etat };; let rec thompson = function | Epsilon -> let e1 = nouvel_´etat () and e2 = nouvel_´etat () in ajoute_eps_trans e1 e2; {initial = e1; final = e2} | Caract`eres cl -> let e1 = nouvel_´etat () and e2 = nouvel_´etat () in do_list (function c -> ajoute_trans e1 c e2) cl; 312 Recherche de motifs dans un texte {initial = e1; final = e2} | Alternative(r1, r2) -> let t1 = thompson r1 and t2 = thompson r2 in let e1 = nouvel_´etat () and e2 = nouvel_´etat () in ajoute_eps_trans e1 t1.initial; ajoute_eps_trans e1 t2.initial; ajoute_eps_trans t1.final e2; ajoute_eps_trans t2.final e2; {initial = e1; final = e2} | S´equence(r1, r2) -> let t1 = thompson r1 and t2 = thompson r2 in ajoute_eps_trans t1.final t2.initial; {initial = t1.initial; final = t2.final} | R´ep´etition r -> let t = thompson r in let e1 = nouvel_´etat () and e2 = nouvel_´etat () in ajoute_eps_trans t.final t.initial; ajoute_eps_trans e1 t.initial; ajoute_eps_trans t.final e2; ajoute_eps_trans e1 e2; {initial = e1; final = e2};; let expr_vers_automate r = let t = thompson r in t.final.terminal <- true; t.initial;; L’algorithme de transformation d’une expression rationnelle en automate employ´e ci-dessus est connu sous le nom de « construction de Thompson ». Les automates qu’il produit ont la particularit´e d’avoir un seul ´etat terminal, qu’on appelle l’´etat final, par sym´etrie avec l’´etat initial. De plus, aucune transition ne sort de l’´etat final. On introduit le type automate_de_thompson pour garder en mˆeme temps un pointeur sur l’´etat initial et un pointeur sur l’´etat final de l’automate. La construction de Thompson proc`ede par r´ecurrence sur la structure de l’expression rationnelle. Pour les deux cas de base, Epsilon et Caract`eres[c1; . . . ; cn], on renvoie les deux automates suivants : " c1 cn ... Clairement, l’automate de gauche reconnaˆıt uniquement le mot vide, et l’automate de droite uniquement les chaˆınes mono-caract`eres c1, . . . , cn. Pour le cas S´equence(r1, r2), on construit r´ecursivement les automates de Thompson correspondant `a r1 et r2, et on met une transition ´etiquet´ee " de l’´etat final de l’automate pour r1 vers l’´etat initial de l’automate pour r2. thompson(r1) thompson(r2) " L’automate r´esultant reconnaˆıt les concat´enations d’un mot reconnu par thompson r1 et d’un mot reconnu par thompson r2. Il reconnaˆıt donc bien les mˆemes mots que l’expression rationnelle S´equence(r1, r2). En suivant un raisonnement semblable, on prend pour les cas Alternative(r1, r2) et R´ep´etition(r) : D´eterminisation de l’automate 313 thompson(r1) thompson(r2) " " " " thompson(r) " " " " La fonction expr_vers_automate est alors tr`es simple : on construit l’automate de Thompson associ´e `a l’expression rationnelle, on marque son ´etat final comme ´etant terminal, et on renvoie l’´etat initial. 16.5 D´eterminisation de l’automate Ayant obtenu un automate qui reconnaˆıt les mˆemes chaˆınes que l’expression rationnelle de d´epart, il nous reste `a programmer une fonction qui teste si une chaˆıne est reconnue ou non par l’automate. Ce test n’est pas imm´ediat en g´en´eral : puisque plusieurs transitions portant le mˆeme caract`ere peuvent sortir d’un mˆeme ´etat, il faut quelquefois essayer plusieurs chemins qui ´epellent la chaˆıne `a reconnaˆıtre. Cependant, il existe une classe d’automates, les automates d´eterministes, pour lesquels le probl`eme est beaucoup plus simple. Un automate d´eterministe a les deux propri´et´es suivantes : 1. il ne poss`ede pas de transition marqu´ee " 2. d’un mˆeme ´etat il ne part jamais plusieurs transitions marqu´ees par le mˆeme caract`ere. Voici par exemple deux automates d´eterministes reconnaissant le, la, les pour celui de gauche, et les entiers en base deux pour celui de droite. l a e s 0 1 0 1 - 0 1 Pour tester si une chaˆıne est reconnue par un automate d´eterministe, il suffit de partir de l’´etat initial et de suivre `a chaque ´etat la transition (unique) marqu´ee par le prochain caract`ere de la chaˆıne. Si on aboutit sur un ´etat terminal, la chaˆıne est reconnue. Si on aboutit sur un ´etat non terminal, ou si on reste bloqu´e en cours de route parce qu’un ´etat n’a pas de transition sur le prochain caract`ere, alors la chaˆıne n’est pas reconnue. La th´eorie des automates montre que pour tout automate il existe un automate d´eterministe qui reconnaˆıt exactement les mˆemes chaˆınes. Nous allons donc commencer par transformer l’automate pr´ec´edemment construit en un automate d´eterministe, puis utiliser cet automate d´eterministe pour d´ecider si une chaˆıne est reconnue ou pas. Voici l’interface du module determ, qui fournit ces deux fonctions. 314 Recherche de motifs dans un texte Fichier determ.mli type ´etat = { mutable dtransitions : transition vect; dterminal : bool } and transition = | Vers of ´etat | Rejet;; value d´eterminise : auto__´etat -> determ__´etat and reconna^ıt : determ__´etat -> string -> bool;; Un ´etat d’un automate d´eterministe est repr´esent´e par un enregistrement `a deux champs : un bool´een dterminal, indiquant si l’´etat est terminal ou non, et un tableau dtransitions `a 256 cases, une par caract`ere du jeu ASCII. Le constructeur Vers indique la pr´esence d’une transition vers l’´etat indiqu´e ; le constructeur Rejet indique l’absence de transition. L’impl´ementation de la fonction reconna^ıt est tr`es simple. Fichier determ.ml exception ´Echec;; let reconna^ıt automate cha^ıne = let ´etat_courant = ref automate in try for i = 0 to string_length cha^ıne - 1 do match !´etat_courant.dtransitions.(int_of_char cha^ıne.[i]) with Rejet -> raise ´Echec | Vers e -> ´etat_courant := e done; !´etat_courant.dterminal with ´Echec -> false;; Le reste du fichier determ.ml est consacr´e `a la fonction de d´eterminisation d’un automate. L’algorithme utilis´e est connu sous le nom de « construction des sousensembles » (subset construction). Les ´etats de l’automate d´eterministe correspondent `a des ensembles d’´etats de l’automate de d´epart : tous les ´etats qu’on peut atteindre `a partir de l’´etat initial en suivant une certaine chaˆıne de caract`eres. L’´etat initial de l’automate d´eterministe est l’ensemble des ´etats qu’on peut atteindre en suivant la chaˆıne vide, c’est-`a-dire l’´etat initial de l’automate de d´epart, plus tous les ´etats qu’on peut atteindre `a partir de l’´etat initial en suivant uniquement des epsilon-transitions (des transitions marqu´ees "). L’´etat correspondant `a l’ensemble d’´etats {e1, . . . , en} est terminal si et seulement si un des ´etats e1, . . . , en est terminal. Pour voir o`u m`ene la transition sur un caract`ere c issue de l’ensemble d’´etats {e1, . . . , en}, on regarde toutes les transitions sur c issues des ´etats e1 `a en dans l’automate initial. Soient f1, . . . , fm les ´etats auxquels elles m`enent. Soient g1, . . . , gp les ´etats accessibles `a partir de f1, . . . , fm en suivant uniquement des epsilon-transitions. On ajoute alors, dans l’automate d´eterministe produit, une transition sur c depuis l’´etat D´eterminisation de l’automate 315 {e1, . . . , en} vers l’´etat {f1, . . . , fm, g1, . . . , gp}. On r´ep`ete ce proc´ed´e jusqu’`a ce qu’il soit impossible d’ajouter de nouvelles transitions. `A titre d’exemple, nous allons d´eterminiser l’automate produit pour l’expression .*toto par la fonction expr_vers_automate. 1 2 3 4 5 6 7 8 9 10 11 12 " tout car. " " " " t " o " t " o Les ´etats sont num´erot´es de 1 `a 12 pour ˆetre rep´er´es plus facilement. `A partir de l’´etat initial 1, on peut atteindre 2, 4 et 5 par epsilon-transitions. L’´etat initial de l’automate d´eterministe est donc {1, 2, 4, 5}. Il est non terminal. 1, 2, 4, 5 Il y a deux types de transitions issues de cet ´etat : la transition sur t et les transitions sur un caract`ere autre que t. Sur t, 2 m`ene `a 3, 5 m`ene `a 6, 2 et 4 ne m`enent `a rien. `A partir de 3, on peut atteindre 2, 4, 5 par epsilon-transitions. `A partir de 6, on peut atteindre 7 par epsilon-transition. On ajoute donc une transition sur t issue de {1, 2, 4, 5} et menant `a {2, 3, 4, 5, 6, 7}. De mˆeme, sur un caract`ere autre que t, 2 m`ene `a 3 et 2, 4, 5 ne m`enent `a rien. A partir de 3, on peut atteindre 2, 4, 5 par epsilontransitions. On ajoute donc des transitions sur tous les caract`eres sauf t, transitions issues de {1, 2, 4, 5} et menant `a {2, 3, 4, 5}. 1, 2, 4, 5 2, 3, 4, 5 2, 3, 4, 5, 6, 7 pas t t En r´ep´etant ce raisonnement jusqu’`a plus soif, on finit par obtenir l’automate d´eterministe suivant : 1, 2, 4, 5 2, 3, 4, 5 2, 3, 4, 5, 6, 7 2, 3, 4, 5, 8, 9 2, 3, 4, 5, 6, 7, 10, 11 2, 3, 4, 5, 8, 9, 12 pas t t pas t t o t o pas o pas t pas o pas t t Seul l’´etat {2, 3, 4, 5, 8, 9, 12} est terminal, puisque c’est le seul `a contenir l’´etat terminal 12 de l’automate de d´epart. 316 Recherche de motifs dans un texte Nous allons maintenant impl´ementer cet algorithme de d´eterminisation en Caml. La premi`ere chose `a faire est de fournir une repr´esentation des ensembles d’´etats, ainsi que les op´erations de base sur ces ensembles. Fichier determ.ml #open "auto";; type ensemble_d’´etats = { contenu : ensent__t; ´el´ements : auto__´etat list };; let vide = { contenu = ensent__vide; ´el´ements = [] };; let est_vide ens = match ens.´el´ements with [] -> true | _ -> false;; let appartient ´etat ens = ensent__appartient ´etat.num´ero ens.contenu;; let ajoute ´etat ens = { contenu = ensent__ajoute ´etat.num´ero ens.contenu; ´el´ements = ´etat :: ens.´el´ements };; Un ensemble d’´etats est repr´esent´e par la liste des ´etats appartenant `a l’ensemble (champ ´el´ements), et par un ensemble d’entiers (champ contenu) : les num´eros des ´etats appartenant `a l’ensemble. On se donne un module ensent qui impl´emente le type ensent__t des ensembles d’entiers. Voici son interface ; on donne en annexe (section 16.7) une impl´ementation possible de ce module. Fichier ensent.mli type t;; value vide : t and appartient : int -> t -> bool and ajoute : int -> t -> t;; Cette repr´esentation apparemment redondante des ensembles d’´etats est bien adapt´ee `a l’utilisation qu’on en fait par la suite : le champ ´el´ements permet d’it´erer facilement sur tous les ´etats d’un ensemble ; le champ contenu permet de tester efficacement l’appartenance et l’´egalit´e entre ensembles. (La primitive d’´egalit´e structurelle = n’est pas utilisable pour comparer des ´etats, parce que la structure qui « pend » sous un ´etat est susceptible de contenir des cycles, qui font boucler l’´egalit´e.) Viennent ensuite les deux op´erations de base de l’algorithme de d´eterminisation : d’une part, ajouter `a un ´etat ou ensemble d’´etats tous les ´etats qu’on peut atteindre par des epsilon-transitions (ce qu’on appelle prendre la « fermeture » d’un ´etat ou ensemble d’´etats) ; d’autre part, d´eterminer les transitions possibles `a partir d’un ensemble d’´etats. Fichier determ.ml let rec ajoute_fermeture ´etat ferm = if appartient ´etat ferm then ferm else list_it ajoute_fermeture ´etat.epsilon_transitions (ajoute ´etat ferm);; let fermeture ´etat = ajoute_fermeture ´etat vide;; D´eterminisation de l’automate 317 let fermeture_ens ens = list_it ajoute_fermeture ens.´el´ements vide;; On appelle fermeture d’un ´etat l’ensemble des ´etats accessibles depuis cet ´etat en z´ero, une ou plusieurs epsilon-transitions. `A partir de l’´etat e, on atteint e et aucun autre ´etat en z´ero epsilon-transitions. En une ou plusieurs transitions, on passe d’abord par un ´etat e′ accessible `a partir de e par une epsilon-transition, puis on atteint un des ´etats appartenant `a la fermeture de e′. Cela sugg`ere la d´efinition suivante de la fonction fermeture : fermeture(e) est l’union du singleton {e} et des ensembles fermeture(e′) pour e′ d´ecrivant e.epsilon_transitions. Cette d´efinition est incorrecte `a cause des cycles possibles : par exemple si e poss`ede une epsilon-transition vers e′ qui a une epsilon-transition vers e. Pour contourner cette difficult´e, l’id´ee est de garder un ensemble des ´etats qu’on sait d’ores et d´ej`a appartenir `a la fermeture. S’il se pr´esente un ´etat e qui n’est pas dans cet ensemble, on l’ajoute et on examine r´ecursivement tous les ´etats de e.epsilon_transitions. Si l’´etat e est d´ej`a dans cet ensemble, il n’y a rien `a faire. La fonction ajoute_fermeture impl´emente cet algorithme. Les fonctions fermeture et fermeture_ens sont de simples applications de fermeture obtenues en prenant l’ensemble vide comme ensemble d’´etats d´ej`a vus. Fichier determ.ml let d´eplacements liste_´etats = let t = make_vect 256 vide in do_list (function ´etat -> do_list (function (car, dest) -> let i = int_of_char car in t.(i) <- ajoute dest t.(i)) ´etat.transitions) liste_´etats; t;; La fonction d´eplacements ci-dessus calcule toutes les transitions possibles `a partir d’un ensemble d’´etats. Elle renvoie un tableau de 256 ensembles d’´etats, qui fait correspondre `a chaque caract`ere l’ensemble des ´etats atteints par transition sur ce caract`ere. Lorsque cet ensemble est vide, cela signifie qu’il n’y a pas de transition possible sur ce caract`ere. Nous pouvons maintenant attaquer la fonction de d´eterminisation proprement dite. L’id´ee de d´epart est simple : pour calculer l’´etat de l’automate d´eterministe correspondant `a un ensemble d’´etats e, on calcule d´eplacements(e), et pour chaque transition possible, on calcule la fermeture de l’ensemble d’´etats destination, puis on construit par un appel r´ecursif l’´etat destination de la transition, qui est l’´etat correspondant `a cette fermeture. Traduisant directement cette approche en Caml Light, on prendrait : let rec d´eterminise ens = { dterminal = exists (function e -> e.terminal) ens.´el´ements; dtransitions = map_vect d´eterm_trans (d´eplacements ens) } and d´eterm_trans dest = if est_vide dest 318 Recherche de motifs dans un texte then Rejet else Vers(d´eterminise(fermeture_ens dest));; Cette approche est malheureusement inadapt´ee `a la structure de graphe des automates : dans le cas d’une transition qui reboucle sur l’´etat dont elle est issue, la fonction d´eterminise va se rappeler `a l’infini sur le mˆeme ensemble d’´etats. Pour contourner cette difficult´e, il faut s´eparer construction d’un nouvel ´etat et calcul des transitions issues de cet ´etat : les ´etats sont cr´e´es initialement sans transitions sortantes, et on les compl`ete ult´erieurement en ajoutant les transitions convenables. Fichier determ.ml let d´eterminise ´etat_initial = let ´etats_connus = hashtbl__new 51 and `a_remplir = stack__new () in let traduire ens = try hashtbl__find ´etats_connus ens.contenu with Not_found -> let nouvel_´etat = { dterminal = exists (function n -> n.terminal) ens.´el´ements; dtransitions = make_vect 256 Rejet } in hashtbl__add ´etats_connus ens.contenu nouvel_´etat; stack__push (ens.´el´ements, nouvel_´etat) `a_remplir; nouvel_´etat in let nouvel_´etat_initial = traduire (fermeture ´etat_initial) in begin try while true do let (liste, nouvel_´etat) = stack__pop `a_remplir in let d´epl = d´eplacements liste in for i = 0 to 255 do if not est_vide d´epl.(i) then nouvel_´etat.dtransitions.(i) <- Vers(traduire (fermeture_ens d´epl.(i))) done done with stack__Empty -> () end; nouvel_´etat_initial;; Le coeur de la fonction de d´eterminisation est la fonction traduire, qui prend en argument un ensemble d’´etats de l’automate de d´epart et renvoie l’´etat correspondant de l’automate d´eterministe. Si l’ensemble d’´etats a d´ej`a ´et´e rencontr´e, on trouve dans la table ´etats_connus l’´etat associ´e. Sinon, on cr´ee un nouvel ´etat, on l’associe `a l’ensemble d’´etats dans la table ´etats_connus, et on le renvoie. Pour ´eviter le bouclage, on n’essaye pas de calculer imm´ediatement les transitions issues du nouvel ´etat : ce dernier est cr´e´e sans aucune transition sortante. On se contente de le mettre dans la pile `a_remplir, qui garde trace des ´etats incomplets, dont il faudra d´eterminer les transitions plus tard. Pour terminer la construction de l’automate, il faut d´epiler les ´etats incomplets, d´eterminer les transitions sortantes en appelant d´eplacements et fermeture_ens, et R´ealisation de la commande grep 319 obtenir les ´etats de destination des transitions en appelant traduire. Les appels `a traduire construisent parfois de nouveaux ´etats ; il faut donc r´ep´eter ce processus jusqu’`a ce que la pile `a_remplir soit vide. (La terminaison est assur´ee parce que le nombre d’ensembles d’´etats possibles est fini : si l’automate initial a n ´etats, il y a au plus 2n ensembles d’´etats diff´erents `a consid´erer.) On amorce le processus en appelant traduire sur la fermeture de l’´etat initial. On obtient ainsi l’´etat initial de l’automate d´eterministe. Comme ses transitions n’ont pas ´et´e calcul´ees, la pile `a_remplir contient cet ´etat. Le premier tour de la boucle while compl`ete cet ´etat comme d´ecrit ci-dessus. Les tours suivants compl`etent les ´etats cr´e´es en cours de route. Quand la pile est vide, stack__pop d´eclenche l’exception stack__Empty, qui fait sortir de la boucle. L’automate d´eterministe est alors complet ; il n’y a plus qu’`a renvoyer son ´etat initial. 16.6 R´ealisation de la commande grep Apr`es cette envol´ee dans le monde des automates, il est temps de revenir sur Terre et de finir l’impl´ementation de la commande grep. La commande grep prend en arguments une expression rationnelle et une liste de noms de fichiers, et affiche toutes les lignes des fichiers qui sont reconnues par l’expression rationnelle. Si aucun nom de fichier n’est fourni, grep lit son entr´ee standard. C’est ce comportement que nous allons maintenant programmer. Les versions de grep qu’on trouve dans les syst`emes Unix proposent un certain nombre d’options qui modifient le comportement de la commande : inverser la recherche (afficher uniquement les lignes qui ne sont pas reconnues), identifier majuscules et minuscules, afficher uniquement le nombre de lignes reconnues, etc. Ces options sont faciles mais fastidieuses `a impl´ementer ; nous les laisserons de cˆot´e. Commen¸cons par deux fonctions qui appliquent un automate sur chaque ligne d’un fichier et affichent les lignes reconnues. Fichier grep.ml #open "expr";; #open "auto";; #open "determ";; let ligne_trouv´ee = ref false;; let grep_sur_canal auto nom_fich canal = try while true do let ligne = input_line canal in if reconna^ıt auto ligne then begin ligne_trouv´ee := true; print_string nom_fich; print_string": "; print_endline ligne end done with End_of_file -> ();; 320 Recherche de motifs dans un texte let grep_sur_fichier auto nom_fich = try let canal = open_in nom_fich in try grep_sur_canal auto nom_fich canal; close_in canal with exc -> close_in canal; raise exc with sys__Sys_error message -> prerr_string "Erreur sur le fichier "; prerr_string nom_fich; prerr_string ": "; prerr_endline message;; La derni`ere phrase du module grep v´erifie que la ligne de commande a la bonne forme, compile l’expression rationnelle en automate d´eterministe, et applique l’automate obtenu sur les fichiers sp´ecifi´es. Fichier grep.ml if sys__interactive then () else if vect_length sys__command_line < 2 then begin prerr_endline "Utilisation: grep "; exit 2 end else begin let expr = try lire (stream_of_string sys__command_line.(1)) with Parse_error | Parse_failure -> prerr_endline "Erreur de syntaxe dans l’expression"; exit 2 in let auto = d´eterminise(expr_vers_automate expr) in if vect_length sys__command_line >= 3 then for i = 2 to vect_length sys__command_line - 1 do grep_sur_fichier auto sys__command_line.(i) done else grep_sur_canal auto "(entr´ee standard)" std_in; exit (if !ligne_trouv´ee then 0 else 1) end;; 16.7 Annexe Pour achever le programme grep, il reste `a impl´ementer le module ensent qui d´efinit le type abstrait des ensembles d’entiers. En voici une impl´ementation simple, `a base de listes croissantes d’entiers. Fichier ensent.ml type t == int list;; let vide = [];; let rec appartient n = function | [] -> false | m::reste -> Mise en pratique 321 if m = n then true else if m > n then false else appartient n reste;; let rec ajoute n = function | [] -> [n] | m::reste as ens -> if m = n then ens else if m > n then n :: ens else m :: ajoute n reste;; Comme la liste est tri´ee par ordre croissant, on arrˆete la recherche ou l’insertion d`es qu’on atteint un ´el´ement plus grand que l’entier `a rechercher ou `a ins´erer. L’insertion et la recherche sont donc en temps moyen n/2 et en temps le pire n, si n est le nombre d’´el´ements de l’ensemble. 16.8 Mise en pratique Il ne reste plus qu’`a compiler tous les modules de la commande grep et `a les lier entre eux. $ camlc -c expr.mli $ camlc -c expr.ml $ camlc -c auto.mli $ camlc -c auto.ml $ camlc -c ensent.mli $ camlc -c ensent.ml $ camlc -c determ.mli $ camlc -c determ.ml $ camlc -c grep.ml $ camlc -o grep expr.zo auto.zo ensent.zo determ.zo grep.zo En guise d’exemple, voici comment rechercher tous les mots qui contiennent la lettre « p » suivie de la lettre « x » dans un fichier : $ camlrun grep ’[pP][a-z]*x’ fichier L’ex´ecution de cette commande sur le texte de ce chapitre d´etecte quatre occurrences du mot « postfixe » et deux de « prix ». 16.9 Pour aller plus loin La rapidit´e d’ex´ecution de la commande grep impl´ement´ee dans ce chapitre pourrait ˆetre fortement am´elior´ee. Il y a deux sources importantes d’inefficacit´e : la d´eterminisation de l’automate, d’une part, et d’autre part l’ex´ecution de l’automate sur les lignes des fichiers. La d´eterminisation est un processus essentiellement coˆuteux : dans le pire des cas, la taille de l’automate produit est exponentielle en la taille de l’expression rationnelle. Dans les cas courants, on l’acc´el`ere consid´erablement en groupant les transitions sortant d’un ´etat et aboutissant sur le mˆeme ´etat. Par exemple, les transitions que nous avons ´etiquet´ees « tout sauf t », « tout sauf e » dans les exemples repr´esentent en fait 255 transitions du mˆeme ´etat vers le mˆeme ´etat. La fonction de d´eterminisation pr´esent´ee 322 Recherche de motifs dans un texte ci-dessus ne tient pas compte de ce genre de partage, et donc a tendance `a refaire 255 fois les mˆemes calculs dans des situations de ce type. L’introduction, dans les automates non d´eterministes, de transitions de la forme « tout sauf . . . » permet d’´eviter cette source d’inefficacit´e, au prix de nombreuses complications dans le programme. Pour ce qui est de l’ex´ecution de l’automate d´eterministe, le probl`eme n’est pas d’ordre algorithmique, mais provient du syst`eme Caml Light lui-mˆeme : il faut ex´ecuter quelque chose de tr`es simple (une boucle, essentiellement) sur un grand volume de donn´ees ; `a ce petit jeu, Caml Light se r´ev`ele nettement plus lent que des compilateurs traditionnels (Caml Light produit du code pour une machine virtuelle, code qui est ensuite interpr´et´e, alors que les compilateurs traditionnels produisent du code directement ex´ecutable par la machine). En ce cas, on gagne beaucoup `a utiliser un compilateur Caml produisant du code machine optimis´e, tel que le compilateur Objective Caml. Bibliographie Pour une bonne pr´esentation des automates vus sous un angle pratique, on se reportera au chapitre 3 de Compilateurs : principes, techniques et outils, de Aho, Sethi et Ullman (Inter´Editions). Pour les lecteurs que les math´ematiques n’effraient pas, signalons que les automates ont beaucoup ´et´e ´etudi´es dans le cadre des langages formels, une des branches les plus anciennes de l’informatique th´eorique. Le livre de Aho et Ullman, Theory of parsing, translation and compiling : 1 : parsing (Addison-Wesley), en donne une bonne vue d’ensemble. III Introspection 17 Ex´ecution d’un langage fonctionnel Quand Caml se regarde le nombril, ou commence `a tenir ses lacets de chaussures. orts de l’exp´erience acquise avec l’impl´ementation du mini-Pascal (chapitre 15), nous entamons ici la description et l’impl´ementation d’un langage fonctionnel simplifi´e, qui est en r´ealit´e un sous-ensemble de Caml et que nous nommerons mini-Caml. Comme d’habitude, nous essaierons de ne pas ´eluder les difficult´es : mini- Caml pr´esente toutes les particularit´es essentielles de Caml (pleine fonctionnalit´e, filtrage, polymorphisme). Cette ´etude nous donnera ´egalement l’occasion d’´eclaircir un certain nombre de points d´elicats du langage Caml lui-mˆeme, aussi bien dans le domaine de l’ex´ecution des programmes que dans celui de la synth`ese des types. Ces points d´elicats se manifestent rarement lorsqu’on programme en Caml (nous ne les avons pas encore rencontr´es dans ce livre), mais apparaissent nettement lorsqu’on impl´emente Caml. Dans ce chapitre, nous commen¸cons notre ´etude par la r´ealisation d’un interpr´eteur mini-Caml non typ´e. Le prochain chapitre aborde la synth`ese et la v´erification statique des types. 17.1 Le langage mini-Caml Comme d’habitude, d´efinissons d’abord la syntaxe abstraite du langage mini-Caml que nous ´etudions. Comme on le voit ci-dessous, nous n’avons conserv´e que les constructions essentielles de Caml : l’acc`es `a un identificateur, la d´efinition de fonctions par filtrage, l’application de fonctions et la liaison let, r´ecursive ou non. S’y ajoutent deux types de base, les entiers et les bool´eens, et deux structures de donn´ees, les paires et les listes. Fichier syntaxe.mli type expression = | Variable of string | Fonction of (motif * expression) list 326 Ex´ecution d’un langage fonctionnel | Application of expression * expression | Let of d´efinition * expression | Bool´een of bool | Nombre of int | Paire of expression * expression | Nil | Cons of expression * expression and motif = | Motif_variable of string | Motif_bool´een of bool | Motif_nombre of int | Motif_paire of motif * motif | Motif_nil | Motif_cons of motif * motif and d´efinition = { r´ecursive: bool; nom: string; expr: expression };; La pauvret´e apparente de ce langage est compens´ee par le fait que de nombreuses constructions de Caml sont d´eriv´ees des constructions de mini-Caml. Par exemple, la construction match e with p1 → e1 . . . n’est autre qu’une application de fonction (function p1 → e1 . . .)(e). De mˆeme, la construction conditionnelle if cond then e1 else e2 se ram`ene `a match cond with true → e1 | false → e2. D’autre part, toutes les op´erations primitives (op´erations arithm´etiques par exemple) se pr´esentent sous la forme d’identificateurs pr´ed´efinis. Une phrase mini-Caml est soit une expression, soit une d´efinition. Contrairement `a Caml, nous n’avons pas de d´eclarations de types. Fichier syntaxe.mli type phrase = | Expression of expression | D´efinition of d´efinition;; value lire_phrase: char stream -> phrase;; L’impl´ementation du module syntaxe est tout enti`ere consacr´ee `a la fonction d’analyse syntaxique lire_phrase. L’analyseur syntaxique de mini-Caml utilise exactement les mˆemes techniques que celui pour mini-Pascal. Nous ne d´etaillerons donc pas la fonction lire_phrase ici, la repoussant sans vergogne `a la fin de ce chapitre pour passer plus vite aux choses s´erieuses. 17.2 L’´evaluateur Repr´esentation des valeurs Notre ´evaluateur manipule des donn´ees tr`es simples du type valeur. Ce sont les valeurs utilis´ees par le langage, aussi bien que les r´esultats des ´evaluations. Les valeurs L’´evaluateur 327 sont de cinq esp`eces possibles : des nombres entiers, des bool´eens, des fonctions, des paires ou des cellules de listes. Les fonctions se divisent en deux classes : les op´erations primitives, qui op`erent directement sur le type valeur, et les fonctions de l’utilisateur. Les fonctions de l’utilisateur sont repr´esent´ees par des fermetures. Une fermeture est une paire dont la premi`ere composante est la d´efinition de la fonction (c’est-`a-dire le filtrage qui calcule le r´esultat de la fonction `a partir de son argument) et la seconde composante est l’environnement qui pr´evalait quand on a d´efinit la fonction. Nous allons voir pourquoi ce codage complexe des valeurs fonctionnelles est ad´equat `a la d´efinition et `a l’ex´ecution des fonctions Caml. Fichier eval.mli #open "syntaxe";; type valeur = | Val_nombre of int | Val_bool´eenne of bool | Val_paire of valeur * valeur | Val_nil | Val_cons of valeur * valeur | Val_fermeture of fermeture | Val_primitive of valeur -> valeur and fermeture = { d´efinition: (motif * expression) list; mutable environnement: environnement } and environnement == (string * valeur) list;; value ´evalue: environnement -> expression -> valeur and ´evalue_d´efinition: environnement -> d´efinition -> environnement and imprime_valeur: valeur -> unit;; exception Erreur of string;; La r`egle de port´ee statique Nous devons donc expliquer pourquoi nous codons les fonctions par des fermetures qui comportent l’environnement de d´efinition de ces fonctions. Ceci est rendu n´ecessaire par la r`egle de « port´ee » des identificateurs en Caml. En effet, tout identificateur est li´e `a la valeur qui l’a d´efini. Cette liaison ne d´epend pas du temps, mais seulement de l’endroit dans le programme o`u l’identificateur est d´efini (c’est pourquoi on parle aussi de port´ee textuelle ou lexicale). Donnons un exemple de ce comportement : nous d´efinissons la constante taille, puis la fonction ajoute_taille qui fait r´ef´erence au nom taille, puis nous red´efinissons le nom taille avec une autre valeur. # let taille = 1;; taille : int = 1 # let ajoute_taille x = x + taille;; ajoute_taille : int -> int = # let taille = 2;; taille : int = 2 328 Ex´ecution d’un langage fonctionnel Le probl`eme est de savoir si la red´efinition de taille a modifi´e la fonction ajoute_taille, ou bien si cette fonction ajoute toujours 1 `a son argument. # ajoute_taille 0;; - : int = 1 Caml suit bien la r`egle de port´ee statique : l’identificateur taille du corps de la fonction fait r´ef´erence `a celui qui ´etait d´efini au moment de la cr´eation de la fonction, pas `a celui qui est d´efini quand on appelle la fonction. En port´ee dynamique, la valeur des identificateurs correspond `a leur derni`ere d´efinition au cours des calculs. La valeur de l’identificateur taille aurait donc ´et´e modifi´ee mˆeme dans le corps de la fonction ajoute_taille. Notre ´evaluateur simule parfaitement ce comportement statique, en attachant au code des fonctions leur environnement de d´efinition. Les lecteurs attentifs auront sans doute remarqu´e qu’une certaine forme de port´ee dynamique peut ˆetre simul´ee en Caml, `a l’aide de r´ef´erences. # let taille = ref 1;; taille : int ref = ref 1 # let ajoute_taille x = x + !taille;; ajoute_taille : int -> int = # taille := 2;; - : unit = () # ajoute_taille 0;; - : int = 2 La liaison de taille `a la r´ef´erence est trait´ee en port´ee statique, mais le contenu de la r´ef´erence est modifiable apr`es la liaison. Cette astuce va mˆeme jusqu’`a la d´efinition de fonctions r´ecursives sans utiliser let rec, `a la mani`ere des langages avec port´ee dynamique. # let fact = let rien = ref (function x -> x) in let f x = if x = 0 then 1 else x * !rien (x - 1) in rien := f; f;; fact : int -> int = # fact 10;; - : int = 3628800 Le code de l’´evaluateur La premi`ere partie de l’´evaluation est consacr´ee `a l’impl´ementation du filtrage. Cette op´eration prend une valeur et un motif, et d´etermine si la valeur est de la forme indiqu´ee par le motif. Si la r´eponse est non, elle d´eclenche l’exception ´Echec_filtrage. Si la r´eponse est oui, elle renvoie un ensemble de liaisons d’identificateurs (les variables du motif) `a des valeurs (les morceaux correspondants de la valeur d’entr´ee), repr´esent´ees par une liste de paires (identificateur, valeur). Fichier eval.ml #open "syntaxe";; exception ´Echec_filtrage;; L’´evaluateur 329 let rec filtrage valeur motif = match (valeur, motif) with | (val, Motif_variable id) -> [id, val] | (Val_bool´eenne b1, Motif_bool´een b2) -> if b1 = b2 then [] else raise ´Echec_filtrage | (Val_nombre i1, Motif_nombre i2) -> if i1 = i2 then [] else raise ´Echec_filtrage | (Val_paire(v1, v2), Motif_paire(m1, m2)) -> filtrage v1 m1 @ filtrage v2 m2 | (Val_nil, Motif_nil) -> [] | (Val_cons(v1, v2), Motif_cons(m1, m2)) -> filtrage v1 m1 @ filtrage v2 m2 | (_, _) -> raise ´Echec_filtrage;; La fonction d’´evaluation d’une expression est remarquablement concise. D´etaillonsen les principales clauses. L’environnement d’´evaluation est repr´esent´e par une liste d’association entre les identificateurs et leurs valeurs. Ceci explique la clause des variables. Pour les fonctions on se contente de cr´eer une fermeture qui emmagasine l’environnement courant (env) au moment de l’´evaluation de la fonction. Pour l’application d’une fonction `a son argument, on ´evalue fonction et argument, et l’on teste si la fonction renvoy´ee est bien une fonction, c’est-`a-dire soit une fermeture, soit une primitive. Dans le cas d’une primitive, on applique directement la valeur fonctionnelle impl´ementant la primitive. Dans le cas d’une fonction, on essaye de filtrer la valeur de l’argument par les motifs des diff´erents cas de la fonction. Le premier filtrage qui r´eussit provoque l’´evaluation de l’expression associ´ee, dans un environnement qui est l’environnement contenu dans la fermeture, enrichi par les liaisons effectu´ees lors du filtrage. Fichier eval.ml let rec ´evalue env expr = match expr with | Variable id -> begin try assoc id env with Not_found -> raise(Erreur(id ^ " est inconnu")) end | Fonction(liste_de_cas) -> Val_fermeture {d´efinition = liste_de_cas; environnement = env} | Application(fonction, argument) -> let val_fonction = ´evalue env fonction in let val_argument = ´evalue env argument in begin match val_fonction with | Val_primitive fonction_primitive -> fonction_primitive val_argument | Val_fermeture fermeture -> ´evalue_application fermeture.environnement fermeture.d´efinition val_argument | _ -> raise(Erreur "application d’une valeur non fonctionnelle") end 330 Ex´ecution d’un langage fonctionnel | Let(d´efinition, corps) -> ´evalue (´evalue_d´efinition env d´efinition) corps | Bool´een b -> Val_bool´eenne b | Nombre n -> Val_nombre n | Paire(e1, e2) -> Val_paire(´evalue env e1, ´evalue env e2) | Nil -> Val_nil | Cons(e1, e2) -> Val_cons(´evalue env e1, ´evalue env e2) and ´evalue_application env liste_de_cas argument = match liste_de_cas with | [] -> raise(Erreur "´echec du filtrage") | (motif, expr) :: autres_cas -> try let env_´etendu = filtrage argument motif @ env in ´evalue env_´etendu expr with ´Echec_filtrage -> ´evalue_application env autres_cas argument and ´evalue_d´efinition env_courant d´ef = match d´ef.r´ecursive with | false -> (d´ef.nom, ´evalue env_courant d´ef.expr) :: env_courant | true -> match d´ef.expr with | Fonction liste_de_cas -> let fermeture = { d´efinition = liste_de_cas; environnement = [] } in let env_´etendu = (d´ef.nom, Val_fermeture fermeture) :: env_courant in fermeture.environnement <- env_´etendu; env_´etendu | _ -> raise(Erreur "let rec non fonctionnel");; Il reste un point d´elicat `a expliquer : la d´efinition r´ecursive. Nous nous sommes limit´es aux cas o`u l’expression d´efinissante est une fonction, ce qui garantit que la d´efinition r´ecursive a toujours un sens. let rec f = function p1 → e1 . . . in . . . La valeur que nous associons `a f est donc une fermeture du corps de la fonction et d’un environnement qui est l’environnement courant d’´evaluation augment´e d’une liaison pour f. En effet, le corps de la fonction (l’expression e1, par exemple) peut faire r´ef´erence `a f, lors d’un appel r´ecursif. L’environnement de la fermeture doit donc contenir une liaison de f `a la valeur de f, c’est-`a-dire `a la fermeture que nous sommes en train de construire. Si nous notons F la fermeture de f, alors l’environnement de cette fermeture doit ˆetre (f, F) :: env_courant. Il est clair que la fermeture F et l’environnement ´etendu qu’elle comporte sont des objets « circulaires ». Pour r´esumer graphiquement ces contraintes : La boucle d’interaction 331 Fermeture { d´efinition = ... ; environnement = • } (f,•) : : env_courant Pour construire la fermeture F, l’astuce est de commencer par construire une fermeture dont le champ environnement est initialis´e `a une valeur quelconque, par exemple l’environnement vide. On construit ensuite l’environnement ´etendu avec cette valeur provisoire. Il suffit alors de modifier physiquement le champ environnement de F pour y stocker l’environnement ´etendu. La modification physique construit le cycle d´esir´e. Impression des valeurs Nous terminons le module eval par une fonction d’impression des valeurs, qui ne pr´esente aucune difficult´e. Fichier eval.ml let rec imprime_valeur = function | Val_nombre n -> print_int n | Val_bool´eenne false -> print_string "false" | Val_bool´eenne true -> print_string "true" | Val_paire(v1, v2) -> print_string "("; imprime_valeur v1; print_string ", "; imprime_valeur v2; print_string ")" | Val_nil -> print_string "[]" | Val_cons(v1, v2) -> imprime_valeur v1; print_string "::"; imprime_valeur v2 | Val_fermeture _ | Val_primitive _ -> print_string "";; 17.3 La boucle d’interaction Nous allons maintenant mettre autour de l’´evaluateur une boucle d’interaction, sur le mod`ele de la boucle d’interaction de Caml. Nous commen¸cons par construire un environnement initial d’´evaluation comprenant un certain nombre de fonctions de base sur les entiers et les bool´eens (arithm´etique, comparaisons, etc.). Pour ce faire, il faut « habiller » les fonctions Caml correspondantes pour qu’elles op`erent non plus sur les types de donn´ees Caml, mais sur leurs repr´esentations dans le type valeur. Fichier interprete.ml #open "syntaxe";; #open "eval";; let code_nombre n = Val_nombre n 332 Ex´ecution d’un langage fonctionnel and d´ecode_nombre = function | Val_nombre n -> n | _ -> raise (Erreur "entier attendu") and code_bool´een b = Val_bool´eenne b and d´ecode_bool´een = function | Val_bool´eenne b -> b | _ -> raise (Erreur "bool´een attendu");; (* Pour transformer une fonction Caml en valeur fonctionnelle *) let prim1 codeur calcul d´ecodeur = Val_primitive(function val -> codeur(calcul(d´ecodeur val))) and prim2 codeur calcul d´ecodeur1 d´ecodeur2 = Val_primitive(function | Val_paire(v1, v2) -> codeur(calcul (d´ecodeur1 v1) (d´ecodeur2 v2)) | _ -> raise(Erreur "paire attendue"));; (* L’environnement initial *) let env_initial = ["+", prim2 code_nombre (prefix + ) d´ecode_nombre d´ecode_nombre; "-", prim2 code_nombre (prefix - ) d´ecode_nombre d´ecode_nombre; "*", prim2 code_nombre (prefix * ) d´ecode_nombre d´ecode_nombre; "/", prim2 code_nombre (prefix / ) d´ecode_nombre d´ecode_nombre; "=", prim2 code_bool´een (prefix = ) d´ecode_nombre d´ecode_nombre; "<>", prim2 code_bool´een (prefix <>) d´ecode_nombre d´ecode_nombre; "<", prim2 code_bool´een (prefix < ) d´ecode_nombre d´ecode_nombre; ">", prim2 code_bool´een (prefix > ) d´ecode_nombre d´ecode_nombre; "<=", prim2 code_bool´een (prefix <=) d´ecode_nombre d´ecode_nombre; ">=", prim2 code_bool´een (prefix >=) d´ecode_nombre d´ecode_nombre; "not", prim1 code_bool´een (prefix not) d´ecode_bool´een; "read_int", prim1 code_nombre (fun x -> read_int ()) d´ecode_nombre; "write_int", prim1 code_nombre (fun x -> print_int x; print_newline (); 0) d´ecode_nombre];; L’´evaluation d’une phrase consiste `a calculer sa valeur et `a l’afficher. Si la phrase est une d´efinition, il faut de plus enrichir l’environnement global par la nouvelle liaison. Fichier interprete.ml let boucle () = let env_global = ref env_initial in let flux_d’entr´ee = stream_of_channel std_in in while true do print_string "# "; flush std_out; try match lire_phrase flux_d’entr´ee with | Expression expr -> let r´es = ´evalue !env_global expr in print_string "- = "; imprime_valeur r´es; Mise en oeuvre 333 print_newline () | D´efinition d´ef -> let nouvel_env = ´evalue_d´efinition !env_global d´ef in begin match nouvel_env with | (nom, val) :: _ -> print_string nom; print_string " = "; imprime_valeur val; print_newline () end; env_global := nouvel_env with | Parse_error | Parse_failure -> print_string "Erreur de syntaxe"; print_newline () | Erreur msg -> print_string "Erreur `a l’´evaluation: "; print_string msg; print_newline () done;; if sys__interactive then () else boucle ();; 17.4 Mise en oeuvre L’interpr`ete mini-Caml se compile comme suit : $ camlc -c syntaxe.mli $ camlc -c eval.mli $ camlc -c eval.ml $ camlc -c lexuniv.mli $ camlc -c lexuniv.ml $ camlc -c syntaxe.ml $ camlc -c interprete.ml $ camlc -o interprete lexuniv.zo syntaxe.zo eval.zo interprete.zo Apr`es lancement par camlrun interprete ou interprete__boucle (), il ne reste plus qu’`a essayer quelques-unes de nos fonctions pr´ef´er´ees. # let rec fib = function n -> match n < 2 with true -> 1 | false -> fib(n - 1) + fib(n - 2);; fib = # fib 10;; - = 89 # let map = function f -> let rec maprec = function [] -> [] | x :: l -> f x :: maprec l in maprec;; map = # map fib (1::2::3::4::5::6::[]);; - = 1::2::3::5::8::13::[] 334 Ex´ecution d’un langage fonctionnel 17.5 Pour aller plus loin Les modes d’´evaluation Vous aurez sans doute remarqu´e que notre ´evaluateur calcule les arguments d’une fonction avant de l’appeler. Cela paraˆıt naturel, mais en fait ce travail s’av`ere inutile dans le cas o`u la fonction n’utilise pas son argument. Pire, il peut se produire que le calcul de l’argument ne termine pas, alors mˆeme qu’il n’est pas utilis´e pour obtenir le r´esultat final. Il arrive donc que notre ´evaluateur ne parvienne pas `a calculer un r´esultat pourtant raisonnable. La m´ethode suivie par notre ´evaluateur est appel´ee l’appel par valeur, puisqu’on appelle les fonctions apr`es avoir calcul´e la valeur de tous leurs arguments. La strat´egie qui consiste `a attendre que le besoin de calculer l’argument se fasse express´ement sentir dans le corps de la fonction s’appelle l’appel par nom. Son inconv´enient majeur est qu’un mˆeme argument est calcul´e plusieurs fois, s’il est utilis´e plusieurs fois dans le corps de la fonction. C’est pourquoi il existe un troisi`eme mode d’appel des fonctions, l’appel par n´ecessit´e, qui consiste, comme dans l’appel par nom, `a attendre que la valeur d’argument soit absolument n´ecessaire avant de le calculer, mais `a mettre en m´emoire cette valeur calcul´ee, pour la r´eutiliser telle quelle `a chaque fois qu’on en a besoin. Des langages fonctionnels comme Haskell fonctionnent selon ce mode de passage des param`etres ; on les appelle les langages paresseux. Leur avantage est ´evidemment qu’ils peuvent terminer des calculs quand un langage en appel par valeur bouclerait ou ´echouerait. D’autre part, ce type de langages permet la manipulation ais´ee de structures de donn´ees potentiellement infinies (on ne calcule, de la structure de donn´ees, que la partie strictement n´ecessaire `a l’obtention du r´esultat final). Un ´ecueil majeur de ces langages est que les effets y sont prohib´es : il est en effet tr`es difficile de pr´edire quand le calcul d’un argument va se d´eclencher, donc impossible de savoir quand vont avoir lieu les effets de bord qu’il comporte ´eventuellement. Des m´ecanismes sp´eciaux comme par exemple les monades sont donc n´ecessaires pour traiter les entr´ees-sorties et les exceptions. L’impl´ementation d’un ´evaluateur pour ce type de langage repose sur la cr´eation de fermetures (plus techniquement appel´ees « suspensions » ou « gla¸cons ») pour les arguments des fonctions : on enferme ainsi le code qui permet de calculer la valeur avec son environnement de d´efinition ; quand on doit ´evaluer une variable, on lance alors l’ex´ecution du code de sa fermeture dans l’environnement qui l’accompagne (d´egel du gla¸con). Dans le cas de l’appel par n´ecessit´e, il faut en fait cr´eer une r´ef´erence qui est mise `a jour en fin de d´egel. Les op´erations primitives testent alors si leurs arguments sont d´ej`a calcul´es ou non. S’ils ne le sont pas, elles les d´eg`elent. Tout comme le langage Caml, notre ´evaluateur fonctionne donc en appel par valeur, mais sa transformation en ´evaluateur par nom n’est pas tr`es difficile. Les d´efinitions r´ecursives Vous aurez remarqu´e que nous limitons la d´efinition de valeurs r´ecursives aux fonctions imm´ediates (c’est-`a-dire directement introduites par le mot-cl´e function). Ce point est raisonnable mais discutable, car on peut donner un sens `a des d´efinitions de valeurs non fonctionnelles. L’´ecueil est de pr´etendre donner aussi un sens `a des d´efinitions qui n’en ont pas, par exemple let rec x = x + 1. Pour aller plus loin 335 En fait, une d´efinition r´ecursive se ram`ene toujours `a la recherche du point fixe d’une certaine fonction. En effet, toute d´efinition r´ecursive est de la forme let rec x = phi(x), ce qui signifie donc que la valeur de x est un point fixe de la fonction phi. Par exemple, pour la d´efinition de la fonction factorielle : let rec fact = function x -> if x = 0 then 1 else x * fact(x - 1), la fonction phi correspondante est function f -> function x -> if x = 0 then 1 else x * f(x - 1). En effet, phi(fact) vaut exactement fact. On montre que la d´efinition de fonctions s’impl´emente correctement dans un langage en appel par valeur. En revanche, pour les autres valeurs, la classe des d´efinitions acceptables n’est pas tr`es claire. Certains syst`emes Caml autorisent la d´efinition r´ecursive de listes boucl´ees (let rec x = 1 :: x). Dans le cas des langages paresseux, on montre qu’il est toujours possible de traiter une d´efinition r´ecursive par it´eration d’une fonction `a partir de la valeur « ind´efini » : voyons l’id´ee qui sous-tend ce m´ecanisme avec la d´efinition de la fonction factorielle. On construit d’abord la fonction repr´esentant l’ind´efini et la fonction phi dont on cherche un point fixe : # let ind´efini x = failwith "ind´efini";; ind´efini : ’a -> ’b = # let phi f = function x -> if x = 0 then 1 else x * f (x - 1);; phi : (int -> int) -> int -> int = Puis on d´efinit les it´erations successives de phi en partant de la valeur ind´efinie : # let fact0 = phi ind´efini;; fact0 : int -> int = # let fact1 = phi fact0;; fact1 : int -> int = # let fact2 = phi fact1;; fact2 : int -> int = # let fact3 = phi fact2;; fact3 : int -> int = Les fonctions fact0, fact1, fact2 et fact3 sont des approximations successives de la fonction factorielle, de plus en plus d´efinies (c’est-`a-dire d´efinies sur un nombre croissant d’entiers) : Argument fact0 fact1 fact2 fact3 0 1 1 1 1 1 ind´efini 1 1 1 2 ind´efini ind´efini 2 2 3 ind´efini ind´efini ind´efini 6 Dans un langage paresseux, les d´efinitions r´ecursives ainsi trait´ees par it´erations successives sont correctes : si le calcul s’arrˆete, alors la valeur calcul´ee sera un point fixe de l’´equation r´ecursive. Au pire, le calcul du point fixe ne termine pas (cas de let rec x = x + 1) ou bien ´echoue, ce qui se traduit par un r´esultat ind´efini (cas de let rec x = x). Les langages paresseux ont donc de belles propri´et´es s´emantiques, mais quelques inconv´enients pratiques dans la programmation de tous les jours. Ils ont de plus une regrettable tendance `a l’inefficacit´e : les arguments de fonctions sont la plupart du 336 Ex´ecution d’un langage fonctionnel temps n´ecessaires `a la fonction, on perd ainsi beaucoup de temps de calcul `a cr´eer des suspensions qu’on ex´ecute presque aussitˆot. Dans les compilateurs, cela est temp´er´e par des analyses de « n´ecessit´e », c’est-`a-dire de d´etection des endroits o`u l’on peut compiler du code qui ´evalue directement un argument qui sera de toutes fa¸cons n´ecessaire dans la suite du code. Ces analyses sont complexes et tr`es difficiles en pr´esence de fonctions d’ordre sup´erieur. 17.6 Annexe Comme promis, nous donnons ici le code de l’analyseur syntaxique pour mini-Caml. La plus grande partie du code est une adaptation directe de l’analyseur syntaxique que nous avions ´ecrit pour le langage mini-Pascal (chapitre 15). Fichier syntaxe.ml #open "lexuniv";; let est_un_op´erateur op´erateurs = function | MC op -> mem op op´erateurs | _ -> false;; let lire_op´erateur op´erateurs = function | [< (stream_check (est_un_op´erateur op´erateurs)) (MC op) >] -> op;; let lire_op´eration lire_base op´erateurs = let rec lire_reste e1 = function | [< (lire_op´erateur op´erateurs) op; lire_base e2; (lire_reste (Application(Variable op, Paire(e1, e2)))) e >] -> e | [< >] -> e1 in function [< lire_base e1; (lire_reste e1) e >] -> e;; let lire_infixe lire_base infixe construire_syntaxe flux = let rec lire_d´ebut = function | [< lire_base e1; (lire_reste e1) e2 >] -> e2 and lire_reste e1 = function | [< (stream_check (function MC op -> op = infixe | _ -> false)) _; lire_d´ebut e2 >] -> construire_syntaxe e1 e2 | [< >] -> e1 in lire_d´ebut flux;; let rec phrase = function | [< d´efinition d; (fin_de_d´efinition d) p; ’MC ";;" >] -> p | [< expression e; ’MC ";;" >] -> Expression e and fin_de_d´efinition d = function | [< ’MC "in"; expression e >] -> Expression (Let(d, e)) | [< >] -> D´efinition d and expression = function | [< d´efinition d; ’MC "in"; expression e >] -> Let(d, e) Annexe 337 | [< ’MC "function"; liste_de_cas liste >] -> Fonction(liste) | [< ’MC "match"; expression e; ’MC "with"; liste_de_cas liste >] -> Application(Fonction(liste), e) | [< expr5 e >] -> e and expr_simple = function | [< ’Entier i >] -> Nombre i | [< ’MC "true" >] -> Bool´een true | [< ’MC "false" >] -> Bool´een false | [< ’Ident id >] -> Variable id | [< ’MC "["; ’MC "]" >] -> Nil | [< ’MC "("; expression e; ’MC ")" >] -> e and expr0 = function | [< expr_simple es; (suite_d’applications es) e >] -> e and suite_d’applications f = function | [< expr_simple arg; (suite_d’applications (Application(f, arg))) e >] -> e | [<>] -> f and expr1 flux = lire_op´eration expr0 ["*"; "/"] flux and expr2 flux = lire_op´eration expr1 ["+"; "-"] flux and expr3 flux = lire_op´eration expr2 ["="; "<>"; "<"; ">"; "<="; ">="] flux and expr4 flux = lire_infixe expr3 "::" (fun e1 e2 -> Cons(e1, e2)) flux and expr5 flux = lire_infixe expr4 "," (fun e1 e2 -> Paire(e1, e2)) flux and d´efinition = function | [< ’MC "let"; r´ecursive r; ’Ident nom; ’MC "="; expression e >] -> {r´ecursive = r; nom = nom; expr = e} and r´ecursive = function | [< ’MC "rec" >] -> true | [< >] -> false and liste_de_cas = function | [< motif m; ’MC "->"; expression e; autres_cas reste >] -> (m, e) :: reste and autres_cas = function | [< ’MC "|"; motif m; ’MC "->"; expression e; autres_cas reste >] -> (m, e) :: reste | [< >] -> [] and motif_simple = function | [< ’Ident id >] -> Motif_variable id | [< ’Entier n >] -> Motif_nombre n | [< ’MC "true" >] -> Motif_bool´een true | [< ’MC "false" >] -> Motif_bool´een false | [< ’MC "["; ’MC "]" >] -> Motif_nil 338 Ex´ecution d’un langage fonctionnel | [< ’MC "("; motif e; ’MC ")" >] -> e and motif1 flux = lire_infixe motif_simple "::" (fun m1 m2 -> Motif_cons(m1,m2)) flux and motif flux = lire_infixe motif1 "," (fun m1 m2 -> Motif_paire(m1,m2)) flux;; let analyseur_lexical = construire_analyseur ["function"; "let"; "rec"; "in"; "match"; "with"; "->"; ";;"; "true"; "false"; "["; "]"; "("; ")"; "::"; "|"; ","; "*"; "/"; "-"; "+"; "="; "<>"; "<"; ">"; "<="; ">="; "::"];; let lire_phrase f = phrase (analyseur_lexical f);; 18 Un synth´etiseur de types Th`ese : le typage est une aide importante pour le programmeur. Antith`ese : mettre les types `a la main dans les programmes, c’est lourd. Synth`ese : utilisons la synth`ese automatique de types ! pr`es l’interpr`ete mini-Caml, nous passons maintenant `a la synth`ese de types pour ce langage. Vous apprendrez ainsi comment fonctionne le contrˆoleur de type de Caml. Cette connaissance vous permettra de mieux comprendre les erreurs de typage qu’il vous signale, particuli`erement sur les points d´elicats de polymorphisme et de circularit´e dans les types. Par-dessus tout, la synth`ese de types est un joli probl`eme de manipulations symboliques de donn´ees et de r´esolution de contraintes. 18.1 Principes de la synth`ese de types Les ´equations entre types La synth`ese de types est analogue `a la r´esolution d’´equations math´ematiques. On aura donc la notion de variables, de constantes, transpos´ee dans le domaine des types : variables de type, types constants. `A la diff´erence des math´ematiques o`u le probl`eme est de r´esoudre un ensemble d’´equations donn´ees `a l’avance, le synth´etiseur de type doit d´ecouvrir dans le programme qui lui est soumis l’ensemble des ´equations `a r´esoudre. Ces ´equations sont cependant tr`es naturelles. Par exemple, si l’on doit typer l’application f(x), on produira les ´equations : type de f = t1 → t2 type de x = t1 type de f(x) = t2 Ici les inconnues sont des types t1 et t2. Ces nouvelles variables seront automatiquement introduites par le synth´etiseur de type. On comprend que si chaque application de fonction introduit deux nouvelles inconnues et trois ´equations suppl´ementaires, le probl`eme m`ene vite `a un tr`es grand nombre d’inconnues et d’´equations. Cependant, l’algorithme de synth`ese de type aura le souci de ne pas introduire de nouvelles inconnues inutilement. Par exemple, si l’on sait d´ej`a que f a pour type ty1 → ty2, on 340 Un synth´etiseur de types se contentera d’utiliser les types ty1 et ty2 qui interviennent d´ej`a dans le probl`eme. De plus, l’algorithme n’attend pas d’avoir enti`erement construit le syst`eme d’´equations pour commencer `a le r´esoudre : il effectue simultan´ement l’introduction des ´equations et leur r´esolution. Polymorphisme et sch´emas de types Comme en math´ematiques, il arrivera que l’ensemble des ´equations n’ait pas une solution unique, mais qu’il y ait au contraire une infinit´e de solutions. C’est g´en´eralement le cas en math´ematiques lorsqu’il y a plus d’inconnues que d’´equations. Consid´erez par exemple le syst`eme d’une ´equation `a deux inconnues x et y x + y = 1. Il poss`ede un degr´e de libert´e : si l’on fixe l’une des inconnues l’autre est calculable instantan´ement. Ce mˆeme ph´enom`ene se rencontre dans les syst`emes d’´equations entre types. Soit par exemple le syst`eme : t1 = int t2 = t3 → t1 qui serait engendr´e par la phrase let f x = 1, o`u t2 est le type de f, t1 celui de 1 et t3 celui de x. Par remplacement de t1, nous en d´eduisons imm´ediatement t1 = int t2 = t3 → int Il est impossible d’aller plus loin dans les remplacements et pourtant le syst`eme poss`ede toujours un degr´e de libert´e : le type t3 peut ˆetre fix´e arbitrairement. Dans le monde des types, on fera alors intervenir le polymorphisme, en disant que la solution pour t2 au syst`eme d’´equations est un sch´ema de type, avec pour param`etre t3. Ce sch´ema de type n’est autre qu’un type de la forme t3 → int valable pour tout les types t3. C’est ce que l’imprimeur des types de Caml ´ecrirait ’a -> int et qui signifie pour tout type ′a, ′a → int. # let f x = 1;; f : ’a -> int = Les types polymorphes sont donc utilis´es pour r´esumer toutes les solutions possibles `a un syst`eme d’´equations entre types qui n’a pas assez de contraintes pour ˆetre r´esolu compl`etement par des types de base. Les variables de types qui deviennent ainsi des param`etres du sch´ema de type sont celles qui ne re¸coivent jamais de valeur et qui ne sont donc soumises `a aucune contrainte dans le programme. M´ethode de r´esolution Pour r´esoudre les syst`emes d’´equations entre types, on utilise en premi`ere approximation le m´ecanisme classique de remplacement des inconnues dont on connaˆıt d´ej`a la valeur. C’est ce que nous venons de faire dans l’exemple pr´ec´edent en rempla¸cant t1 par la valeur connue int. En fait, le m´ecanisme de r´esolution est plus g´en´eral : c’est une m´ethode de propagation de contraintes d’´egalit´e connue sous le nom de m´ecanisme d’unification. Nous le verrons en d´etail par la suite. Principes de la synth`ese de types 341 Pour mod´eliser les syst`emes d’´equations nous aurons donc besoin de variables (de type) pour les inconnues du syst`eme, de types constants pour les constantes du syst`eme et d’op´erations entre types comme la fl`eche -> ou le produit *. Nous aurons ´egalement besoin de mod´eliser les ´equations elles-mˆemes (le signe =, pourrait-on dire) et donc de rendre compte du remplacement d’une variable par sa valeur. Ce remplacement sera compl`etement automatique car nous utiliserons le partage : toutes les occurrences d’une inconnue dans les ´equations du syst`eme `a r´esoudre seront repr´esent´ees physiquement par le mˆeme objet en m´emoire. Remplacer partout l’inconnue par une valeur reviendra simplement `a modifier le contenu de l’objet qui repr´esente l’inconnue en y d´eposant la valeur. Pratiquement, nos variables auront donc deux statuts possibles : elles seront soit des inconnues (n’ayant donc jamais re¸cu de valeur), soit des variables connues ayant donc une valeur associ´ee. Par exemple pour le syst`eme (r´esolu) t1 = int t2 = t3 → int t1 sera une variable connue valant int, t2 une variable connue valant t3 → int, tandis que t3 sera toujours une variable inconnue. Utilisation des sch´emas de types Le polymorphisme est donc mod´elis´e par des sch´emas de type. Chaque fois qu’on doit utiliser un sch´ema de type, on se contente d’utiliser le type qui d´ecrit le sch´ema, avec de nouvelles inconnues. Par exemple, si l’on veut utiliser le sch´ema Pour tout type ′a, ′a → int, on utilisera le type ty → int o`u ty est une nouvelle inconnue. Le sch´ema de type correspond `a l’ensemble de tous les types obtenus en rempla¸cant ′a par un type quelconque. Une fois ′a remplac´ee par une nouvelle inconnue, cette inconnue pourra recevoir n’importe quelle valeur par la suite. Ainsi, la fonction d´efinie par let f x = 1 a pour sch´ema de type Pour tout type ′a, ′a → int. Supposons qu’on ´ecrive (f 1, f "oui"). On emploiera deux fois le sch´ema de type de f, une fois avec une nouvelle variable t1 (t1 → int), puis avec une autre variable t2 (t2 → int). Une fois cette substitution de nouvelles inconnues dans le sch´ema de type effectu´ee, les deux occurrences de f sont munies d’un type « comme les autres » (non pas un sch´ema) et tout se d´eroule comme avant. Dans notre exemple, l’application de f `a 1 engendre l’´equation t1 = int, tandis que l’application de f `a la chaˆıne "oui" engendre l’´equation t2 = string. Les inconnues t1 et t2 re¸coivent ainsi une valeur et l’on en d´eduit facilement que le type de l’expression (f 1, f "oui") est int * int. # (f 1, f "oui");; - : int * int = 1, 1 Les sch´emas de types sont donc des artifices utilis´es tr`es ponctuellement par l’algorithme de typage : il ne les manipule pas directement pour r´esoudre les ´equations. En effet, quand on emploie un sch´ema de type, on remplace syst´ematiquement ses param`etres par des inconnues « normales ». L’algorithme de r´esolution ne manipulera donc que des expressions de types comprenant des inconnues, mais non pas des sch´emas. 342 Un synth´etiseur de types Introduction des sch´emas de types Nous avons vu que le synth´etiseur de types avait deux activit´es principales : introduire de nouvelles ´equations et de nouvelles inconnues et r´esoudre les syst`emes d’´equations qu’il a lui-mˆeme engendr´es. Quand faut-il se contenter d’engranger des ´equations et `a quel moment faut-il d´ecider d’entrer dans la phase de r´esolution ? Une premi`ere r´eponse simple serait : il faut r´esoudre en fin de phrase. C’est exact, mais insuffisant. En effet, lorsqu’on ´ecrit en Caml let identit´e x = x in (identit´e 1, identit´e "oui");; il faut d´ecider quel est le type de identit´e avant de typer la partie in. En effet, si (et c’est le cas ici) le nom d´efini par « let » poss`ede un type polymorphe (dont certaines variables restent des inconnues), il faut les d´etecter tout de suite pour pouvoir employer ce nom avec diff´erents types dans la partie in. C’est cette r´esolution partielle de l’ensemble des ´equations de typage qui rend la synth`ese de type difficile. Il nous faudra donc r´esoudre les contraintes avant de typer la partie in d’une expression let. Plus pr´ecis´ement, il nous faudra seulement d´ecouvrir le type de l’identificateur d´efini, au cas o`u ce type comporterait des param`etres et serait donc polymorphe. Ici intervient un ph´enom`ene que nous admettrons, nous contentant de le justifier intuitivement : seules les inconnues introduites pendant le typage de la d´efinition sont susceptibles de devenir des param`etres du type de l’identificateur d´efini. Les autres inconnues (celles qui apparaissaient dans le syst`eme avant le typage de la d´efinition) ne doivent pas devenir des param`etres, car elles peuvent intervenir plus tard dans des contraintes de types engendr´ees par le reste du programme. Consid´erons ce programme : function x -> let y = x in x+y Juste avant de typer la partie in, les ´equations de typage sont type de x = t1 type de y = t1 et il serait incorrect de conclure que y a le type Pour tout ′a, ′a, puisque le reste du programme va r´ev´eler que t1 = int. En revanche, on d´emontre que toutes les inconnues qui ont ´et´e introduites pendant le typage de la d´efinition et qui sont toujours inconnues `a la fin du typage de la d´efinition, ne seront pas modifi´ees plus tard par ajout de contraintes suppl´ementaires ; nous sommes donc fond´es `a mettre un « pour tout » devant ces inconnues, les transformant en param`etres du sch´ema de type. Dans le synth´etiseur de types, nous aurons donc un m´ecanisme pour retrouver facilement toutes les inconnues introduites pendant le typage d’une d´efinition. L’id´ee est simplement d’associer un « ˆage » aux inconnues, refl´etant la date `a laquelle elles ont ´et´e introduites. Remarquons que les d´efinitions d’identificateurs (par let) sont les seules constructions qui engendrent des sch´emas de type : c’est pourquoi on dit souvent qu’en Caml seul le let donne du polymorphisme. En particulier, les arguments de fonctions n’ont jamais de type polymorphe. Ceci vient directement de la structure des types manipul ´es en Caml : il est impossible d’exprimer avec ces types qu’un argument de fonction doit ˆetre polymorphe. En effet les sch´emas de types de Caml sont de la forme Pour tous types ′a, ′b, . . . Type, ce qui signifie que tous les param`etres d’un sch´ema de Principes de la synth`ese de types 343 type sont plac´es en tˆete de ce sch´ema (quantification pr´enexe). On ne peut donc pas exprimer le type d’une fonction dont l’argument serait polymorphe (donc li´e `a un sch´ema de type) comme celui d’une fonction qui exigerait que son argument soit au moins aussi polymorphe que l’identit´e : (Pour tout type ′a, ′a →′ a) → . . . Cela explique pourquoi la fonction suivante est mal typ´ee : # let phi identit´e = (identit´e 1, identit´e "oui");; Entr´ee interactive: >let phi identit´e = (identit´e 1, identit´e "oui");; > ^^^^^ Cette expression est de type string, mais est utilis´ee avec le type int. On pourrait avoir l’id´ee d’utiliser une construction let pour cr´eer une variable polymorphe ´egale `a l’argument de la fonction phi : # let phi identit´e = let id = identit´e in (id 1, id "oui");; Entr´ee interactive: > (id 1, id "oui");; > ^^^^^ Cette expression est de type string, mais est utilis´ee avec le type int. Cela ne marche pas car le type de id est l’inconnue correspondant au type de l’identificateur identit´e ; or cette inconnue a ´et´e cr´e´ee avant la d´efinition de id, lors de l’introduction du param`etre de la fonction phi. On pourrait encore d´efinir localement une fonction de mˆeme s´emantique que l’argument de phi, en supposant que son type, n’´etant plus directement celui de identit´e, deviendrait polymorphe (il s’agit ici d’une -expansion). # let phi identit´e = let id x = identit´e x in (id 1, id "oui");; Entr´ee interactive: > (id 1, id "oui");; > ^^^^^ Cette expression est de type string, mais est utilis´ee avec le type int. Encore une fois, id reste monomorphe, car le synth´etiseur de type n’a pas g´en´eralis´e le type de id, qui provenait d’une sp´ecialisation du type de identit´e, l’argument de la fonction phi : les inconnues cr´e´ees pour construire le type de id sont aussi « vieilles » que le type dont elles proviennent (dans l’algorithme de typage la vieillesse est h´er´editaire). R´ep´etons cette r`egle fondamentale du typage de Caml : seul le let donne du poly- morphisme. Cela a des cons´equences ´etranges pour les fonctions anonymes : elles ne sont jamais polymorphes. C’est pourquoi des phrases s´emantiquement ´equivalentes sont susceptibles d’avoir des types diff´erents. On sait par exemple que (function x -> e2) e1 produit le mˆeme r´esultat que let x = e1 in e2. C’est parfaitement vrai en ce qui concerne l’ex´ecution. C’est faux en ce qui concerne le typage, puisque l’identificateur x n’est jamais polymorphe dans la version avec function, tandis que la version avec let l’y autorise. La construction match . . . with met en ´evidence le mˆeme ph´enom`ene 344 Un synth´etiseur de types puisqu’elle est ´equivalente `a l’application d’une fonction anonyme. Nous comprenons mieux maintenant le typage des phrases ´equivalentes suivantes : # (function y -> y y) (function x -> x);; Entr´ee interactive: >(function y -> y y) (function x -> x);; > ^ Cette expression est de type ’a -> ’b, mais est utilis´ee avec le type ’a. # let y = function x -> x in y y;; - : ’_a -> ’_a = # match (function x -> x) with y -> y y;; Entr´ee interactive: >match (function x -> x) with y -> y y;; > ^ Cette expression est de type ’a -> ’b, mais est utilis´ee avec le type ’a. 18.2 L’algorithme de synth`ese de types Nous programmons maintenant l’algorithme de synth`ese de types proprement dit, la partie du synth´etiseur qui examine le programme et produit les ´equations entre types qui d´eterminent les types de tous les objets du programme. Nous verrons plus tard comment ces ´equations sont r´esolues et comment les expressions de types sont repr´esent´ees de mani`ere `a manipuler efficacement les ´equations entre types. Pour l’instant, nous nous contentons de donner l’interface du module types, qui d´efinit deux types de donn´ees abstraits, type_simple et sch´ema_de_types, ainsi que toutes les op´erations sur ces types dont nous avons besoin. Fichier types.mli type type_simple and sch´ema_de_types;; value type_int: type_simple and type_bool: type_simple and type_fl`eche: type_simple -> type_simple -> type_simple and type_produit: type_simple -> type_simple -> type_simple and type_liste: type_simple -> type_simple;; value nouvelle_inconnue: unit -> type_simple and unifie: type_simple -> type_simple -> unit and g´en´eralisation: type_simple -> sch´ema_de_types and sp´ecialisation: sch´ema_de_types -> type_simple and sch´ema_trivial: type_simple -> sch´ema_de_types and d´ebut_de_d´efinition: unit -> unit and fin_de_d´efinition: unit -> unit;; exception Conflit of type_simple * type_simple and Circularit´e of type_simple * type_simple;; value imprime_type: type_simple -> unit L’algorithme de synth`ese de types 345 and imprime_sch´ema: sch´ema_de_types -> unit;; Voici une br`eve description des op´erations du module types. Nous donnerons plus de d´etails par la suite, mais pour l’instant il est inutile d’en savoir plus. type_int, type_bool les constantes de types int et bool. type_fl`eche t1 t2 renvoie la repr´esentation du type t1 -> t2. type_produit t1 t2 renvoie la repr´esentation du type t1 * t2. type_liste t renvoie la repr´esentation du type t list. nouvelle_inconnue () cr´ee une nouvelle inconnue de typage. unifie t1 t2 enregistre l’´equation t1 = t2 et la r´esout compte tenu des ´equations d´ej`a enregistr´ees. Conflit, Circularit´e exceptions d´eclench´ees par unifie lorsqu’on lui donne une ´equation qui n’a pas de solution (comme int = bool). g´en´eralisation t transforme le type t en un sch´ema de types, avec pour param`etres toutes les inconnues introduites lors du typage de la pr´ec´edente d´efinition. sp´ecialisation s transforme le sch´ema de types s en un type simple, en rempla¸cant les param`etres du sch´ema par de nouvelles inconnues de typage. sch´ema_trivial t transforme le type t en un sch´ema de types « trivial », c’est-`a-dire sans aucun param`etre. Sert `a m´elanger dans la mˆeme structure d’environnement de « vrais » sch´emas de types, tels que ceux obtenus par la construction let, et des types simples, tels que les types des arguments des fonctions. d´ebut_de_d´efinition () signale que l’on commence le typage de la partie d´efinition d’un let. fin_de_d´efinition () signale que l’on sort du typage de la partie d´efinition d’un let et qu’on est sur le point de typer la partie in. imprime_type t affiche le type t `a l’´ecran. imprime_sch´ema s mˆeme chose pour un sch´ema. Le module synthese fournit deux fonctions, l’une qui d´etermine le type d’une expression, l’autre qui d´etermine le type d’une d´efinition. Les deux fonctions sont param´etr´ees par un environnement de typage, qui associe des sch´emas de types aux identificateurs libres de l’expression. Fichier synthese.mli #open "syntaxe";; #open "types";; type environnement == (string * sch´ema_de_types) list;; value type_exp: environnement -> expression -> type_simple and type_d´ef: environnement -> d´efinition -> environnement;; exception Erreur of string;; 346 Un synth´etiseur de types L’impl´ementation du module synthese commence par une fonction auxiliaire de typage des motifs, qui sert `a d´eterminer le type d’une fonction. Fichier synthese.ml #open "syntaxe";; #open "types";; let rec type_motif env = function | Motif_variable id -> let ty = nouvelle_inconnue () in (ty, (id, sch´ema_trivial ty) :: env) | Motif_bool´een b -> (type_bool, env) | Motif_nombre n -> (type_int, env) | Motif_paire(m1, m2) -> let (ty1, env1) = type_motif env m1 in let (ty2, env2) = type_motif env1 m2 in (type_produit ty1 ty2, env2) | Motif_nil -> (type_liste (nouvelle_inconnue ()), env) | Motif_cons(m1, m2) -> let (ty1, env1) = type_motif env m1 in let (ty2, env2) = type_motif env1 m2 in unifie (type_liste ty1) ty2; (ty2, env2);; La fonction type_motif renvoie deux r´esultats : d’une part, le type du motif (c’est- `a-dire le type des valeurs qu’on a le droit de filtrer par ce motif) ; d’autre part, un environnement de typage ´etendu, associant des inconnues de typage aux variables du motif. Par exemple, le motif x::t a pour type t1 list, o`u t1 est une inconnue (puisqu’en l’absence d’information sur les utilisations de x et de t, on ne sait rien de plus sur le type des listes filtr´ees par ce motif) et on ´etend l’environnement avec x de type t1 et t de type t1 list. La fonction type_motif ne pr´esente pas de difficult´es majeures. Le seul cas qui introduit une contrainte de typage est le cas des motifs « cons » m1 :: m2. Dans ce cas, il faut imposer que le type de m2 soit un type liste dont les ´el´ements ont pour type celui du motif m1. Nous passons maintenant au typage des expressions. Commentons bri`evement les cas int´eressants. Pour une variable, on va chercher son sch´ema de types dans l’environnement de typage et on « sp´ecialise » ce sch´ema en rempla¸cant ses param`etres par de nouvelles inconnues. Pour une fonction, on type successivement chacun des cas du filtrage qui la d´efinit. Les types des motifs doivent ˆetre ´egaux au type de l’argument de la fonction. Les types des expressions associ´ees doivent ˆetre ´egaux au type du r´esultat de la fonction. Pour chaque cas, la partie expression est typ´ee dans l’environnement courant ´etendu par le typage du motif. Pour une application, le type de la partie fonction doit ˆetre un type fl`eche t1 -> t2, avec t1 ´egal au type de la partie argument ; t2 nous donne alors le type du r´esultat de l’application. Pour un let, l’essentiel du travail est fait par la fonction type_d´ef que nous expliquons juste apr`es le code. Les autres constructions se typent de mani`ere ´evidente. L’algorithme de synth`ese de types 347 Fichier synthese.ml let rec type_exp env = function | Variable id -> begin try sp´ecialisation (assoc id env) with Not_found -> raise(Erreur(id ^ " est inconnu")) end | Fonction liste_de_cas -> let type_argument = nouvelle_inconnue () and type_r´esultat = nouvelle_inconnue () in let type_cas (motif, expr) = let (type_motif, env_´etendu) = type_motif env motif in unifie type_motif type_argument; let type_expr = type_exp env_´etendu expr in unifie type_expr type_r´esultat in do_list type_cas liste_de_cas; type_fl`eche type_argument type_r´esultat | Application(fonction, argument) -> let type_fonction = type_exp env fonction in let type_argument = type_exp env argument in let type_r´esultat = nouvelle_inconnue () in unifie type_fonction (type_fl`eche type_argument type_r´esultat); type_r´esultat | Let(d´ef, corps) -> type_exp (type_d´ef env d´ef) corps | Bool´een b -> type_bool | Nombre n -> type_int | Paire(e1, e2) -> type_produit (type_exp env e1) (type_exp env e2) | Nil -> type_liste (nouvelle_inconnue ()) | Cons(e1, e2) -> let type_e1 = type_exp env e1 in let type_e2 = type_exp env e2 in unifie (type_liste type_e1) type_e2; type_e2 and type_d´ef env d´ef = d´ebut_de_d´efinition (); let type_expr = match d´ef.r´ecursive with | false -> type_exp env d´ef.expr | true -> let type_provisoire = nouvelle_inconnue () in let type_expr = type_exp ((d´ef.nom, sch´ema_trivial type_provisoire) :: env) d´ef.expr in unifie type_expr type_provisoire; type_expr in fin_de_d´efinition (); (d´ef.nom, g´en´eralisation type_expr) :: env;; Le typage des d´efinitions s´epare nettement le cas r´ecursif du cas ordinaire. Dans le cas ordinaire, on type simplement l’expression qui d´efinit l’identificateur et l’on ajoute le sch´ema de types correspondant (obtenu par la fonction g´en´eralisation) `a 348 Un synth´etiseur de types l’environnement de typage. Les appels `a d´ebut_de_d´efinition et fin_de_d´efinition qui entourent le typage de l’expression d´efinissante permettent `a g´en´eralisation de d´etecter les inconnues qui doivent ˆetre g´en´eralis´ees. Dans le cas r´ecursif, le m´ecanisme est analogue, mais on prend la pr´ecaution de pr´eenregistrer l’identificateur avec une nouvelle inconnue, avant de typer l’expression. On unifie ensuite le type pr´eenregistr´e avec le type effectivement trouv´e pour l’expression d´efinissante. Comme dans le cas des fonctions, l’identificateur d´efini r´ecursivement est pr´eenregistr´e avec un type inconnu ; il n’est donc pas polymorphe dans l’expression d´efinissante. Ceci vous explique pourquoi la fonction identit´e re¸coit ici un type monomorphe : # let rec identit´e x = x and message s = print_string (identit´e s);; identit´e : string -> string = message : string -> unit = 18.3 Repr´esentation des types Passons `a l’impl´ementation du module types. Les types simples et les sch´emas de types sont repr´esent´es comme suit. Fichier types.ml type type_simple = | Variable of variable_de_type | Terme of string * type_simple vect and variable_de_type = { mutable niveau: int; mutable valeur: valeur_d’une_variable } and valeur_d’une_variable = | Inconnue | Connue of type_simple;; type sch´ema_de_types = { param`etres: variable_de_type list; corps: type_simple };; let type_int = Terme("int", [||]) and type_bool = Terme("bool", [||]) and type_fl`eche t1 t2 = Terme("->", [|t1; t2|]) and type_produit t1 t2 = Terme("*", [|t1; t2|]) and type_liste t = Terme("list", [|t|]);; Les types simples sont soit des variables de type, soit des types construits. Une variable de type poss`ede un niveau de liaison, qui identifie la d´efinition o`u la variable a ´et´e cr´e´ee, et une valeur. Cette valeur est soit inconnue (la variable de type est toujours une inconnue), soit connue ; en ce cas c’est un type simple. Un type construit se compose d’un constructeur de type, comme int ou ->, et le cas ´ech´eant des arguments du Repr´esentation des types 349 constructeur, sous forme d’un tableau de types simples. Les types de base comme int et bool ont z´ero argument, les types listes ont un argument et les types fl`eches et produits en ont deux. Pour fournir l’intuition de la m´ethode de r´esolution, nous donnons une interpr ´etation graphique de cette m´ethode. Nous expliquons donc graphiquement comment les ´equations de typage sont prises en compte grˆace `a cette repr´esentation des types et des variables de type. Nous repr´esentons les variables de type par des boˆıtes qui pointent vers une valeur : soit la valeur Inconnue si la variable reste une inconnue, soit le type correspondant. Par exemple, dans le sch´ema suivant, t1 est une inconnue tandis que t2 vaut int. t1 Inconnue t2 int Nous repr´esentons les types construits soit simplement par leur nom quand ils ne poss`edent pas d’arguments, soit par une boˆıte comportant des pointeurs vers les arguments du type. Voici les repr´esentations du type int → int et du type t1 → int quand t1 est une inconnue : → int int → t1 Inconnue int Modification physique directe des variables de type ´E tudions le d´eroulement de la r´esolution du syst`eme d’´equations : t1 = int t2 = t1 → int Au d´epart, nous introduisons les deux inconnues t1 et t2. t1 Inconnue t2 Inconnue La prise en compte de t1 = int s’effectue par simple modification physique du champ valeur de l’inconnue t1, pour le faire pointer vers le type int. t1 Inconnue t2 Inconnue t1 int t2 Inconnue t1 devient int 350 Un synth´etiseur de types Le traitement de l’´equation t2 = t1 → int est similaire. t1 int t2 Inconnue t1 int t2 → t1 int int t2 devient t1 → int Comme nous l’avons dit, les types sont partag´es, ce qui implique que la modification d’un type entraˆıne automatiquement la modification de tous les types dans lesquels il intervient. Nous allons voir ce m´ecanisme `a l’oeuvre dans la r´esolution du syst`eme pr´ec´edent, en supposant simplement que les deux ´equations sont pr´esent´ees dans l’ordre inverse. On commence donc par prendre en compte l’´equation t2 = t1 → int et t1 reste une inconnue. t1 Inconnue t2 Inconnue t1 Inconnue t2 → t1 Inconnue int t2 devient t1 → int Maintenant, la prise en compte de l’´equation t1 = int modifie automatiquement le type t1 → int, ce qui a l’effet suivant : t1 Inconnue → t1 Inconnue int t1 int → t1 int int t1 devient int Finalement, grˆace au ph´enom`ene de partage, la r´esolution produit exactement le mˆeme r´esultat quel que soit l’ordre dans lequel on r´esout les ´equations. Repr´esentation des types 351 t1 Inconnue t2 → t1 Inconnue int t1 int t2 → t1 int int t1 devient int En r´esum´e, la r´esolution du syst`eme produit toujours l’effet suivant : t1 Inconnue t2 Inconnue t1 int t2 → t1 int int t1 devient int et t2 devient t1 → int Modification physique des valeurs point´ees Voici un exemple plus difficile, o`u les modifications physiques doivent s’op´erer sur les valeurs point´ees par les variables et non sur les variables elles-mˆemes. t1 = t2 t1 = t3 t2 = int La prise en compte de l’´equation t1 = t2 modifie physiquement la variable t1, qui pointe maintenant vers t2. Cette derni`ere reste une inconnue. t1 est donc maintenant li´ee `a une autre inconnue. t1 Inconnue t2 Inconnue t3 Inconnue t1 t2 Inconnue t2 Inconnue t3 Inconnue t1 devient t2 La prise en compte de l’´equation t1 = t3 est plus complexe : puisque t1 pointe sur t2, donc que t1 poss`ede maintenant une valeur, il ne faut surtout pas modifier na¨ıvement 352 Un synth´etiseur de types t1, ce qui aurait pour effet d’oublier la contrainte t1 = t2 en faisant pointer directement t1 vers t3. La modification hˆative de t1 pour lui donner la valeur de t3 produirait deux erreurs : premi`erement, la contrainte t1 = t2 serait oubli´ee au passage, comme nous l’avons dit ; de plus la contrainte induite t2 = t3 ne serait pas prise en compte, puisque t2 resterait une inconnue. t1 t2 Inconnue t2 Inconnue t3 Inconnue t1 t3 Inconnue t2 Inconnue t3 Inconnue Traitement erron´e de t1 devient t3 La contrainte t1 = t2 est oubli´ee Au contraire, il faut suivre le pointeur qui donne la valeur de t1, ce qui conduit `a t2, et modifier alors t2. La modification physique correcte est donc la suivante : t1 t2 Inconnue t2 Inconnue t3 Inconnue t1 t2 t3 Inconnue t2 t3 Inconnue t3 Inconnue t1 devient t3 La prise en compte de t2 = int est similaire : on modifie simplement la variable vers laquelle pointe t2, c’est-`a-dire t3, et t1 est modifi´ee par effet. t1 t2 t3 Inconnue t2 t3 Inconnue t3 Inconnue t1 t2 t3 int t2 t3 int t3 int t2 devient int La conclusion de cette discussion est qu’il est g´en´eralement incorrect d’op´erer sur une variable dont la valeur est connue : il faut directement op´erer sur la valeur de cette variable. Bien sˆur, si la valeur est elle-mˆeme une variable connue, il faut aller chercher la valeur suivante. C’est la tˆache de la fonction valeur_de : sauter par-dessus les variables connues jusqu’`a obtenir soit un terme, soit une variable inconnue. L’unification 353 Fichier types.ml let rec valeur_de = function | Variable({valeur = Connue ty1} as var) -> let valeur_de_ty1 = valeur_de ty1 in var.valeur <- Connue valeur_de_ty1; valeur_de_ty1 | ty -> ty;; La fonction valeur_de profite de sa recherche de la valeur d’une inconnue pour raccourcir le chemin qui m`ene `a cette valeur. (Le lecteur cultiv´e aura reconnu la structure de donn´ees union-find et l’op´eration de path compression.) En reprenant l’exemple des trois variables de types t1, t2 et t3, voici graphiquement l’effet physique d’un appel de valeur_de sur la variable t1 : t1 t2 t3 int t2 t3 int t3 int t1 int t2 int t3 int Effets de l’application de valeur_de `a t1 valeur_de t1 renvoie int et modifie t1 et t2 Pr´evention des cycles Un autre ´ecueil concerne l’´equation toute simple t1 = t1. Pour la prendre en compte, il suffit bien entendu de ne rien faire. Cependant il faut pr´evoir explicitement ce cas dans les programmes, sous peine de faire pointer la variable de type t1 vers elle-mˆeme et qui plus est de pr´etendre que t1 n’est plus une inconnue puisqu’elle a une valeur. Voici ce que donnerait une modification hˆative de t1 pour la faire pointer vers t1. t1 Inconnue t1 Traitement erron´e de t1 = t1 (t1 deviendrait cyclique) 18.4 L’unification L’unification est le moteur de la r´esolution des ´equations de typage. Elle consiste `a r´esoudre un ensemble d’´equations, en donnant aux variables de type qui interviennent 354 Un synth´etiseur de types dans le probl`eme des valeurs qui rendent toutes les ´equations vraies. ´Etant donn´ee notre repr´esentation des ´equations, l’unification revient `a prendre deux types et `a les rendre ´egaux si n´ecessaire, en attribuant des valeurs convenables aux inconnues qui apparaissent dans ces types. Le test d’occurrence Avant d’aborder l’unification, il nous faut expliquer une derni`ere subtilit´e du typage de Caml : le test d’occurrence. Il consiste `a v´erifier qu’une inconnue n’est pas pr´esente dans un type dont elle doit prendre la valeur. Cela entre dans le cadre de la pr´evention des cycles, dans un cas de figure plus subtil que l’affectation d’une variable `a elle-mˆeme. Ce test sert donc `a garantir que les types manipul´es par l’algorithme de typage sont toujours des arbres ne comportant pas de cycles. En effet, notre algorithme bouclerait si les types qui lui sont soumis devenaient cycliques en cours de typage. Avant de donner `a une inconnue la valeur d’un type, on v´erifie donc que cette inconnue n’apparaˆıt pas dans le type. Le syst`eme Caml fait la mˆeme v´erification, comme le prouve l’exemple suivant : # let double f = f f;; Entr´ee interactive: >let double f = f f;; > ^ Cette expression est de type ’a -> ’b, mais est utilis´ee avec le type ’a. La fonction test_d’occurrence prend donc une variable en argument, puis le type qu’on veut lui attribuer et op`ere une descente r´ecursive dans les arguments de ce type pour v´erifier qu’aucun ne contient cette variable. Fichier types.ml let test_d’occurrence var ty = let rec test t = match valeur_de t with | Variable var’ -> if var == var’ then raise(Circularit´e(Variable var, ty)) | Terme(constructeur, arguments) -> do_vect test arguments in test ty;; Pour tester l’´egalit´e entre la variable dont on cherche les occurrences et une autre variable, la fonction test_d’occurrence ne doit pas utiliser la fonction d’´egalit´e structurelle de Caml : en effet, toutes les variables qui sont encore inconnues ont la mˆeme structure (elles pointent toutes vers le constructeur Inconnue) et sont donc structurellement ´egales. On utilise donc le test d’´egalit´e physique, qui indique que ces arguments sont rang´es `a la mˆeme place en m´emoire (ce qui assure que ces arguments sont un seul et mˆeme objet). L’op´erateur de test d’´egalit´e physique de deux valeurs Caml est pr´ed´efini et not´e == (l’in´egalit´e physique est not´ee !=). Ainsi, quand le test var == var’ rend vrai, cela indique que var et var’ sont en fait la mˆeme variable : dans ce cas on d´eclenche l’exception Circularit´e avec pour arguments la variable et le type qu’on voulait lui affecter. L’unification 355 Mise `a jour des niveaux des variables Chaque variable porte donc un « niveau », qui indique dans quelle d´efinition elle a ´et´e cr´e´ee. Plus le niveau est ´elev´e, plus la variable a ´et´e introduite r´ecemment. Lorsqu’on affecte une variable v par un type t, il faut pr´eserver cette information. En particulier, si le type t contient des variables de niveau plus ´elev´e que v, il faut abaisser le niveau de ces variables au niveau de v. Tout doit se passer comme si, au lieu d’avoir introduit une variable `a une certaine date puis d´etermin´e sa valeur par r´esolution de contraintes, on avait devin´e la valeur correcte au moment de l’introduction de la variable. La fonction rectifie_niveaux garantit cette propri´et´e. Fichier types.ml let rec rectifie_niveaux niveau_max ty = match valeur_de ty with | Variable var -> if var.niveau > niveau_max then var.niveau <- niveau_max | Terme(constructeur, arguments) -> do_vect (rectifie_niveaux niveau_max) arguments;; Le moteur de la r´esolution La fonction qui unifie deux types proc`ede par filtrage sur ces types et ne rend pas de valeur : elle fait les affectations n´ecessaires ou bien elle ´echoue. Elle envisage donc tous les cas possibles de deux valeurs du type type_simple. Les deux premiers cas du filtrage concernent le cas d’une variable libre `a unifier avec un type, et son cas sym´etrique. Le dernier cas correspond `a deux types construits. Lorsqu’un des types est une variable, il suffit de modifier physiquement cette variable pour la rendre ´egale `a l’autre type. Comme expliqu´e ci-dessus, il faut cependant effectuer le test d’occurrence et remettre `a jour les niveaux des variables dans le type. Lorsque les deux types sont des types construits, de deux choses l’une : ou bien leurs constructeurs sont ´egaux, et alors il suffit d’unifier r´ecursivement leurs arguments pour rendre les deux types ´egaux ; ou bien leurs constructeurs sont diff´erents, auquel cas l’´equation n’a pas de solutions et l’exception Conflit est d´eclench´ee. Il reste un dernier cas, qui est intercept´e au tout d´ebut de la fonction unifie : lorsque les deux types sont d´ej`a ´egaux, et tout particuli`erement lorsqu’ils repr´esentent la mˆeme variable, il n’y a rien `a faire. Pour d´etecter cette situation, nous prenons les « valeurs » des deux types, en supprimant les variables connues. Si les valeurs sont physiquement ´egales, les deux types sont ´egaux et l’unification s’arrˆete aussitˆot. Fichier types.ml let rec unifie ty1 ty2 = let valeur1 = valeur_de ty1 and valeur2 = valeur_de ty2 in if valeur1 == valeur2 then () else match (valeur1, valeur2) with | Variable var, ty -> test_d’occurrence var ty; rectifie_niveaux var.niveau ty; var.valeur <- Connue ty 356 Un synth´etiseur de types | ty, Variable var -> test_d’occurrence var ty; rectifie_niveaux var.niveau ty; var.valeur <- Connue ty | Terme(constr1, arguments1), Terme(constr2, arguments2) -> if constr1 <> constr2 then raise (Conflit(valeur1, valeur2)) else for i = 0 to vect_length arguments1 - 1 do unifie arguments1.(i) arguments2.(i) done;; 18.5 Inconnues, g´en´eralisation et sp´ecialisation Le module type tient `a jour le « niveau de liaison » courant des inconnues. Ce niveau est incr´ement´e au d´ebut du typage d’une d´efinition et d´ecr´ement´e `a la fin. Il mesure la profondeur d’imbrication `a gauche des constructions let. Les nouvelles inconnues sont cr´e´ees avec le champ niveau ´egal au niveau courant de liaison. Fichier types.ml let niveau_de_liaison = ref 0;; let d´ebut_de_d´efinition () = incr niveau_de_liaison and fin_de_d´efinition () = decr niveau_de_liaison;; let nouvelle_inconnue () = Variable {niveau = !niveau_de_liaison; valeur = Inconnue};; L’op´eration de g´en´eralisation consiste `a trouver, dans le type `a g´en´eraliser, toutes les variables dont le niveau est strictement plus grand que le niveau courant de liaison. En supposant qu’on appelle g´en´eralisation juste apr`es fin_de_d´efinition, ces variables sont exactement les inconnues introduites pendant le typage de la derni`ere d´efinition. La fonction g´en´eralisation en construit la liste (en faisant bien attention `a ne pas mettre plusieurs fois la mˆeme variable dans la liste) ; cette liste constitue la liste des param`etres du sch´ema de type renvoy´e par g´en´eralisation. Fichier types.ml let g´en´eralisation ty = let params = ref [] in let rec trouve_param`etres ty = match valeur_de ty with | Variable var -> if var.niveau > !niveau_de_liaison && not memq var !params then params := var :: !params | Terme(constr, arguments) -> do_vect trouve_param`etres arguments in trouve_param`etres ty; {param`etres = !params; corps = ty};; Impression des types 357 let sch´ema_trivial ty = {param`etres = []; corps = ty};; L’op´eration de sp´ecialisation consiste `a associer une nouvelle inconnue `a chaque param`etre du sch´ema, puis `a faire une copie du corps du sch´ema en rempla¸cant les variables qui sont des param`etres par l’inconnue qui leur est associ´ee. La recherche de l’inconnue associ´ee `a un param`etre de type donn´e a lieu dans la liste d’association nouvelles_inconnues, `a l’aide de la fonction pr´ed´efinie assq, similaire `a assoc mais utilisant le test d’´egalit´e physique == au lieu du test d’´egalit´e structurelle =. Fichier types.ml let sp´ecialisation sch´ema = match sch´ema.param`etres with | [] -> sch´ema.corps | params -> let nouvelles_inconnues = map (fun var -> (var, nouvelle_inconnue ())) params in let rec copie ty = match valeur_de ty with | Variable var as ty -> (try assq var nouvelles_inconnues with Not_found -> ty) | Terme(constr, arguments) -> Terme(constr, map_vect copie arguments) in copie sch´ema.corps;; 18.6 Impression des types Il nous reste `a ´ecrire les fonctions d’impression des types. C’est un peu long mais sans r´eelle difficult´e. La seule astuce consiste `a produire de jolis noms pour les variables de type (’a, ’b, . . . ). Nous avons ´egalement simplifi´e le code en supposant que les constructeurs de types ont au plus deux arguments et que ceux qui ont deux arguments se notent de mani`ere infixe (comme -> et *). Ce n’est pas vrai en g´en´eral, mais c’est le cas en mini-Caml. Fichier types.ml let noms_des_variables = ref ([] : (variable_de_type * string) list) and compteur_de_variables = ref 0;; let imprime_var var = print_string "’"; try print_string (assq var !noms_des_variables) with Not_found -> let nom = make_string 1 (char_of_int(int_of_char ‘a‘ + !compteur_de_variables)) in incr compteur_de_variables; noms_des_variables := (var, nom) :: !noms_des_variables; print_string nom;; let rec imprime ty = 358 Un synth´etiseur de types match valeur_de ty with | Variable var -> imprime_var var | Terme(constructeur, arguments) -> match vect_length arguments with | 0 -> print_string constructeur | 1 -> imprime arguments.(0); print_string " "; print_string constructeur | 2 -> print_string "("; imprime arguments.(0); print_string " "; print_string constructeur; print_string " "; imprime arguments.(1); print_string ")";; let imprime_type ty = noms_des_variables := []; compteur_de_variables := 0; imprime ty;; let imprime_sch´ema sch´ema = noms_des_variables := []; compteur_de_variables := 0; if sch´ema.param`etres <> [] then begin print_string "pour tout "; do_list (fun var -> imprime_var var; print_string " ") sch´ema.param`etres; print_string ", " end; imprime sch´ema.corps;; 18.7 La boucle d’interaction Pour finir, il ne nous reste plus qu’`a mettre une boucle d’interaction autour du synth´etiseur de types. La boucle est un d´ecalque exact de celle utilis´ee pour l’interpr`ete mini-Caml du chapitre pr´ec´edent. Fichier typeur.ml #open "syntaxe";; #open "types";; #open "synthese";; let type_arithm´etique = sch´ema_trivial (type_fl`eche (type_produit type_int type_int) type_int) and type_comparaison = sch´ema_trivial (type_fl`eche (type_produit type_int type_int) type_bool);; let env_initial = ["+", type_arithm´etique; "-", type_arithm´etique; "*", type_arithm´etique; "/", type_arithm´etique; "=", type_comparaison; "<>", type_comparaison; "<", type_comparaison; ">", type_comparaison; Mise en oeuvre 359 "<=", type_comparaison; ">=", type_comparaison; "not", sch´ema_trivial(type_fl`eche type_bool type_bool); "read_int", sch´ema_trivial(type_fl`eche type_int type_int); "write_int", sch´ema_trivial(type_fl`eche type_int type_int)];; let boucle () = let env_global = ref env_initial in let flux_d’entr´ee = stream_of_channel std_in in while true do print_string "# "; flush std_out; try match lire_phrase flux_d’entr´ee with | Expression expr -> let ty = type_exp !env_global expr in print_string "- : "; imprime_type ty; print_newline () | D´efinition d´ef -> let nouvel_env = type_d´ef !env_global d´ef in begin match nouvel_env with | (nom, sch´ema) :: _ -> print_string nom; print_string " : "; imprime_sch´ema sch´ema; print_newline () end; env_global := nouvel_env with | Parse_error | Parse_failure -> print_string "Erreur de syntaxe"; print_newline () | Conflit(ty1, ty2) -> print_string "Incompatibilit´e de types entre "; imprime_type ty1; print_string " et "; imprime_type ty2; print_newline () | Circularit´e(var, ty) -> print_string "Impossible d’identifier "; imprime_type var; print_string " et "; imprime_type ty; print_newline () | Erreur msg -> print_string "Erreur de typage: "; print_string msg; print_newline () done;; if sys__interactive then () else boucle ();; 18.8 Mise en oeuvre L’ensemble du programme se compile par les commandes suivantes. $ camlc -c syntaxe.mli $ camlc -c types.mli $ camlc -c types.ml $ camlc -c synthese.mli $ camlc -c synthese.ml 360 Un synth´etiseur de types $ camlc -c lexuniv.mli $ camlc -c lexuniv.ml $ camlc -c syntaxe.ml $ camlc -c typeur.ml $ camlc -o typeur types.zo synthese.zo lexuniv.zo syntaxe.zo typeur.zo Le programme se lance par camlrun typeur ou typeur__boucle () et inf`ere vaillamment les types de nos fonctionnelles pr´ef´er´ees. # let rec map = function f -> function [] -> [] | a :: l -> f a :: map f l;; map : pour tout ’a ’b , ((’b -> ’a) -> (’b list -> ’a list)) # map (function x -> x + 1) (1 :: 2 :: 3 :: []);; - : int list # map (function x -> x) [];; - : ’a list # map (function x -> not x) (1 :: 2 :: []);; Incompatibilit´e de types entre bool et int # let id = function x -> x in id (id (id));; - : (’a -> ’a) 18.9 Pour aller plus loin Complexit´e de l’algorithme de synth`ese de types Il est tr`es difficile de faire l’analyse de la complexit´e de l’algorithme de typage que nous avons pr´esent´e. C’est si vrai qu’il a fallu attendre plus de dix ans avant que cette ´etude soit r´ealis´ee par P. Kanellakis, H. Mairson et J. Mitchell, dans l’article «Unification and ML type reconstruction », Computational Logic : Essays in Honor of Alan Robinson, MIT Press, 1991. Avant cette publication, il ´etait « bien connu » dans la communaut´e des chercheurs que la synth`ese de type d’un langage comme Caml ´etait lin´eaire. Ce « r´esultat » purement intuitif venait uniquement de l’exp´erience, car on « voyait » bien que les compilateurs se comportaient normalement : le temps de compilation doublait approximativement quand la taille du programme doublait. Le r´esultat th´eorique est stup´efiant : le typage des programmes est en fait exponentiel, et mˆeme doublement exponentiel dans notre cas. Or nous savons qu’un algorithme exponentiel est catastrophiquement lent quand la taille des donn´ees augmente. Le caract`ere doublement exponentiel de l’algorithme de typage devrait le rendre compl`etement inutilisable en pratique. Et pourtant, `a quelques nuances pr`es, cet algorithme est celui qu’on utilise dans les impl´ementations de Caml et l’on constate (vous pouvez constater) qu’il est assez efficace. La raison en est que le comportement exponentiel, pour ind´eniable qu’il soit, est extrˆemement pathologique. La source de difficult´e r´eside dans le polymorphisme, mais uniquement pour des programmes dont le polymorphisme est extraordinaire, voire d´eraisonnable. Pour montrer qu’une phrase peut induire du polymorphisme de fa¸con exponentielle par rapport `a sa taille, nous utiliserons la source la plus simple de polymorphisme, la liste vide et la paire. Nous d´efinissons une expression par une cascade de let . . . in. `Achaque ´ etage les param`etres du sch´ ema de type de l’´ etage pr´ ec´ edent sont dupliqu´ es. Pour aller plus loin 361 Un ´etage suppl´ementaire multiplie donc par deux le nombre de param`etres de type du r´esultat. # let x0 = [] in x0,x0;; - : ’a list * ’b list = [], [] # let x0 = [] in let x1 = x0,x0 in x1,x1;; - : (’a list * ’b list) * (’c list * ’d list) = ([], []), ([], []) Pla¸cons-nous directement au niveau 3 et arrˆetons-nous l`a, parce que les types grossissent trop vite. # let x0 = [] in let x1 = x0,x0 in let x2 = x1,x1 in let x3 = x2,x2 in x3,x3;; - : (((’a list * ’b list) * (’c list * ’d list)) * ((’e list * ’f list) * (’g list * ’h list))) * (((’i list * ’j list) * (’k list * ’l list)) * ((’m list * ’n list) * (’o list * ’p list))) = ((([], []), ([], [])), (([], []), ([], []))), ((([], []), ([], [])), (([], []), ([], []))) On montre facilement qu’au niveau 10 on aura 210 (soit 1024) param`etres de type et que le type du r´esultat occupera presque 28000 caract`eres (15 ´ecrans de 24 lignes sur 80 colonnes). Dans ce cas le typage du programme est bien plus long que son ex´ecution : l’ex´ecution est instantan´ee, puisqu’il suffit de construire 10 cellules de paires ! La paire (le constructeur infixe « , ») n’est pas responsable de ce ph´enom`ene : il est possible de le reproduire en n’utilisant que des fonctions. Il existe en effet un codage fonctionnel de la paire : l’id´ee consiste `a consid´erer une paire comme un objet qui r´epond aux messages « premi`ere composante » et « seconde composante » ; c’est donc une fonction qui applique une projection `a ses deux composantes, `a charge pour la projection de s´electionner la composante qui l’int´eresse. # let paire x y = function projection -> projection x y;; paire : ’a -> ’b -> (’a -> ’b -> ’c) -> ’c = # let fst paire = paire (function x -> function y -> x) and snd paire = paire (function x -> function y -> y);; fst : ((’a -> ’b -> ’a) -> ’c) -> ’c = snd : ((’a -> ’b -> ’b) -> ’c) -> ’c = # let paire_un_true proj = paire 1 true proj;; paire_un_true : (int -> bool -> ’a) -> ’a = # fst paire_un_true;; - : int = 1 # snd paire_un_true;; - : bool = true On reproduit alors exactement les mˆemes exemples que ci-dessus, sans utiliser de structures de donn´ees. Assez curieusement, la complexit´e de l’algorithme de typage a deux sources : la recherche du type de la phrase ´evidemment, mais aussi la simple impression du type r´esultat. On montre en effet que la repr´esentation interne du type des phrases sans let est toujours fortement partag´ee : la taille du type est au plus lin´eaire par rapport `a la taille du programme. Si donc on prend soin de ne pas d´epartager les types pendant 362 Un synth´etiseur de types le typage et qu’on ´ecrit les types en exhibant leur partage, l’algorithme de synth`ese de type devient lin´eaire pour les expressions qui ne comportent pas de let. Lorsqu’on utilise la construction let, il n’existe pas d’algorithme de typage lin´eaire. La taille du type d’une phrase comportant n constructions let est susceptible d’atteindre 2n. Pire, si l’on ne prend pas soin d’imprimer les types avec le partage, alors la taille du type produit (en nombre de caract`eres) peut atteindre 22n ! Voici un exemple de programme produisant ce comportement pathologique. # let paire x = function proj -> proj x x;; paire : ’a -> (’a -> ’a -> ’b) -> ’b = # let x0 y = paire (paire y);; x0 : ’a -> (((’a -> ’a -> ’b) -> ’b) -> ((’a -> ’a -> ’b) -> ’b) -> ’c) -> ’c = # let x1 y = x0 (x0 y);; x1 : ’a -> (((((((’a -> ’a -> ’b) -> ’b) -> ((’a -> ’a -> ’b) -> ’b) -> ’c) -> ’c) -> ((((’a -> ’a -> ’b) -> ’b) -> ((’a -> ’a -> ’b) -> ’b) -> ’c) -> ’c) -> ’d) -> ’d) -> ((((((’a -> ’a -> ’b) -> ’b) -> ((’a -> ’a -> ’b) -> ’b) -> ’c) -> ’c) -> ((((’a -> ’a -> ’b) -> ’b) -> ((’a -> ’a -> ’b) -> ’b) -> ’c) -> ’c) -> ’d) -> ’d) -> ’e) -> ’e = Nous n’irons pas plus loin sous peine de remplir ce livre avec les types de cette s´erie. Vous continuerez vous-mˆeme avec : let x2 y = x1 (x1 y);; let x3 y = x2 (x2 y);; let x4 y = x3 (x3 y);; `A titre indicatif, le type de x2 d´epasse les 72 lignes de 80 caract`eres, celui de x3 les 18000 lignes (en fait 1441777 caract`eres ou environ 300 pages de ce livre !). Pour x4 nous vous laissons attendre le r´esultat, s’il vous int´eresse . . . Retenons qu’il existe des programmes Caml de quelques lignes (mettons trois) qui demandent un temps de typage exorbitant. Nous avons donc la preuve que cet algorithme est au moins exponentiel dans le pire des cas. Sa complexit´e en moyenne est difficile `a estimer (qu’est-ce qu’un programme Caml «moyen » de taille n ?). Nous sommes donc en pr´esence d’un algorithme ayant une complexit´e extrˆemement ´elev´ee dans le pire des cas, mais une complexit´e lin´eaire en pratique (c’est-`a-dire pour les donn´ees qu’on lui donne effectivement `a traiter). Dans le pire des cas, tout se passe comme si on parvenait `a soumettre `a l’algorithme des donn´ees compl`etement improbables, sur lesquelles il pr´esente une complexit´e maximale. En pratique, les programmes Caml qu’on ´ecrit vraiment sont peu polymorphes et dans ce cas l’algorithme de typage est effectivement lin´eaire. Finalement, le pire n’est pas forc´ement le plus probable, heureusement ! Concept g´en´eral d’unification Pour r´esoudre les ´equations de typage, nous avons introduit la notion d’unification. Cette notion n’est pas restreinte aux probl`emes d’´equations entre types : elle se d´efinit dans le cadre plus g´en´eral des alg`ebres de termes (c’est-`a-dire des structures math´ematiques minimales o`u l’on puisse parler de variables, de constantes et de fonctions d’arit´e fix´ee). C’est aussi le m´ecanisme d’´evaluation de base des langages de programmation logique tels que Prolog. Pour aller plus loin 363 Typage des valeurs mutables Nous n’avons pas abord´e le typage des traits imp´eratifs de Caml. Si la compilation des valeurs mutables et de l’affectation n’est pas un probl`eme difficile, leur typage polymorphe est un probl`eme qui a fait couler beaucoup d’encre. La difficult´e vient essentiellement du polymorphisme et de la g´en´eralisation des valeurs dont on peut changer dynamiquement le type par affectation, en invalidant ainsi les hypoth`eses du contrˆoleur de type. Prenons comme exemple, le cas des r´ef´erences. Le type naturel de ref est Pour tout type ′a, ′a → ′a ref, celui de l’affectation est Pour tout type ′a, ′a ref → ′a → unit et enfin le d´er´ef´erencement a pour type Pour tout type ′a, ′a ref → ′a. Remarquez que ces types sont polymorphes. Consid´erez le programme suivant : # let y = ref [] in y := true :: !y; y := 1 :: !y; !y;; Entr´ee interactive: > y := 1 :: !y; > ^^ Cette expression est de type bool list, mais est utilis´ee avec le type int list. Le contrˆoleur de type a tr`es soigneusement ´evit´e de g´en´eraliser le type de la variable y. `A d´efaut, il aurait obtenu le type Pour tout type ′a, ′a list ref. En ce cas, la premi`ere affectation aurait ins´er´e true dans la liste point´ee par y et la seconde aurait ´et´e accept´ee, ins´erant un entier dans la mˆeme liste. Cela aurait ´evidemment invalid´e l’hypoth`ese fondamentale que les listes sont homog`enes. Cependant, si le contrˆoleur de type avait suivi les r`egles habituelles, cette g´en´eralisation aurait dˆu avoir lieu, comme dans l’exemple similaire sans r´ef´erences. # let y = [] in let z = true :: y in 1 :: y;; - : int list = [1] De nombreux algorithmes ont ´et´e propos´es pour typer les valeurs mutables. Tous tentent d’´eviter la cr´eation de valeurs mutables polymorphes, en restreignant le polymorphisme au niveau de la construction let. Nous n’´etudierons pas ces algorithmes qui ne sont pas simples et sans doute pas encore d´efinitifs. Il existe cependant une m´ethode tr`es simple permettant de r´egler ce probl`eme : elle consiste `a changer l’algorithme de base, bien entendu au niveau du typage du let, en d´ecidant que toutes les expressions ne sont pas g´en´eralisables : on ne g´en´eralise que les constantes, les variables et les fonctions imm´ediates. La preuve de correction de cet algorithme pour les valeurs mutables est facile `a apporter : il n’y a jamais de cr´eation de valeurs mutables polymorphes, puisque le polymorphisme est r´eserv´e `a des expressions qui ne peuvent pas cr´eer de valeurs mutables. C’est en effet clair pour les constantes et les variables. Pour les fonctions imm´ediates c’est aussi ´evident : ces fonctions sont celles directement introduites par le mot-cl´e function ; on n’´evalue donc rien lors de leur d´efinition. 364 Un synth´etiseur de types L’inconv´enient de cette m´ethode est qu’elle modifie l’algorithme de base ; en particulier elle refuse de g´en´eraliser les applications, quelles qu’elles soient. Cela interdit de d´efinir une fonction polymorphe par application partielle : let map_id = map identit´e;; est alors typ´e de fa¸con monomorphe. En pratique, ce n’est pas si grave car il suffit de faire une -expansion, en ajoutant un param`etre suppl´ementaire. On ´ecrirait let map_id l = map identit´e l;; Cette derni`ere phrase n’est pas vraiment plus complexe que l’application partielle ; on peut mˆeme la juger plus claire. L’avantage fondamental de cette m´ethode est sa grande simplicit´e : on conserve les mˆemes types qu’avant l’introduction des valeurs mutables et les fonctions manipulant les valeurs mutables sont, sans danger, compl`etement polymorphes. C’est d’ailleurs la m´ethode adopt´ee actuellement dans les compilateurs Caml : # let identit´e x = x;; identit´e : ’a -> ’a = # let map_id_poly l = map identit´e l;; map_id_poly : ’a list -> ’a list = # let map_id = map identit´e;; map_id : ’_a list -> ’_a list = La variable de type not´ee ’_a par le syst`eme Caml correspond exactement aux variables de type inconnues de notre contrˆoleur de type et, comme elles, la variable ’_a est susceptible de recevoir un type par unification dans la suite du typage du programme : # map_id [1; 2; 3];; - : int list = [1; 2; 3] # map_id;; - : int list -> int list = La modification de notre contrˆoleur de type pour qu’il ob´eisse `a cette mani`ere de traiter les valeurs mutables polymorphes est un exercice facile que nous laissons au lecteur. 19 En guise de conclusion Tout a une fin, mais ce n’est pas triste . . . n conclusion de ce livre, nous aimerions r´efl´echir sur les id´ees g´en´erales qui se d´egagent de l’ensemble des programmes des deuxi`eme et troisi`eme parties de notre ouvrage. Et pour terminer en beaut´e, nous esquisserons `a grands traits ce que pourrait ˆetre l’impl´ementation d’un compilateur Caml, en passant rapidement en revue les principales difficult´es sp´ecifiques `a la compilation des langages fonctionnels. 19.1 Une m´ethodologie de programmation En ´etudiant les exemples pr´esent´es dans ce livre, vous avez pu constater que la d´emarche ´etait souvent la mˆeme : nous d´efinissions d’abord une structure de donn´ees, la syntaxe abstraite, puis un moyen commode de faire produire par Caml des valeurs de ce type, la syntaxe concr`ete avec son analyseur lexico-syntaxique. Apr`es ces deux ´etapes en guise de pr´eambule, nous passions aux choses s´erieuses, `a savoir le travail sur la syntaxe abstraite et son interpr´etation par des programmes d’analyse s´emantique. Cette m´ethodologie a commenc´e tr`es tˆot avec le crayon ´electronique et le langage mini- Logo et s’est poursuivie ensuite sans discontinuer dans des domaines aussi divers que la d´emonstration de tautologies avec son langage des formules, la commande grep avec son langage d’expressions rationnelles, la pico-machine avec son langage d’assemblage, et bien entendu le mini-Pascal et le mini-Caml dont les langages associ´es ´etaient directement des langages de programmation usuels. Dans tous les cas, nous ´etions ramen´es `a d´efinir et impl´ementer un langage, aussi bien en ce qui concerne la syntaxe (abstraite et concr`ete) que la s´emantique. Cette m´ethodologie est tr`es g´en´erale et f´econde. Par exemple, un grand nombre de commandes du syst`eme d’exploitation Unix se pr´esentent sous la forme de petits langages sp´ecialis´es ; c’est ´egalement le cas d’´editeurs de textes comme Emacs, de traitements de textes comme TEX, et mˆeme des langages HTML et XML de description de pages Web. C’est pourquoi nous avons abondamment illustr´e cette m´ethodologie, pour vous permettre de la reconnaˆıtre dans les probl`emes de programmation qui se pr´esenteront `a vous. 366 En guise de conclusion Les deux modes d’´evaluation Si le volet syntaxique de cette m´ethode est relativement invariant d’une application `a l’autre, le volet s´emantique se subdivise en deux grandes classes : l’interpr´etation et la compilation. Dans les deux cas, la s´emantique consiste en un calcul de valeurs associ´ees aux arbres de syntaxe abstraite. Mais ce calcul s’effectue soit directement, et il s’agit alors d’interpr´etation (´evaluateur des tautologies, du langage graphique, de mini-Caml) ; soit en deux ´etapes corr´el´ees, en produisant d’abord une nouvelle donn´ee `a partir de l’arbre de syntaxe abstraite, puis en ´evaluant cette nouvelle donn´ee, et il s’agit maintenant de compilation (compilation d’un automate `a partir d’une expression rationnelle ou production de code pour la pico-machine `a partir d’un programme Pascal). G´en´eralement, les s´emantiques `a compilateur sont plus efficaces que celles `a interpr ´eteur, car la phase de compilation permet d’une part d’anticiper et de pr´eparer la phase d’´evaluation et d’autre part de mettre en facteur certaines parties r´ep´etitives de l’´evaluation. Dans le cas d’un langage de programmation, cet avantage de la compilation est particuli`erement clair : lorsque l’interpr´eteur doit ´evaluer un programme, il lui faut constamment analyser l’arbre de syntaxe abstraite, alors qu’un code compil´e n’a plus de questions `a se poser : le compilateur a fait une fois pour toute l’analyse. L’exemple des boucles est frappant `a cet ´egard : l’interpr`ete r´eanalyse le corps de la boucle `a chaque tour de boucle, alors que le code compil´e ex´ecute directement ce corps. Compilation et interactivit´e Int´eressons-nous plus particuli`erement `a la derni`ere partie de ce livre, celle qui concerne l’« introspection » de Caml. Nous avons donn´e une s´emantique `a interpr´eteur pour mini-Caml. Nous l’avons fait `a des fins p´edagogiques, mais ce n’est pas une m´ethode r´ealiste d’impl´ementation de Caml : tous les syst`emes Caml reposent sur des compilateurs. Cela peut surprendre dans la mesure o`u tous les syst`emes Caml proposent aussi une boucle d’interaction `a l’utilisateur. Or, il est clair qu’un interpr`ete est plus adapt´e `a l’´evaluation interactive, puisqu’il calcule directement la s´emantique du programme, alors qu’un compilateur s´epare nettement la production du code compil´e de son ex´ecution, rendant apparemment impossible l’obtention imm´ediate du r´esultat du programme. Cependant, tous les syst`emes Caml disposent d’une boucle d’interaction sans interpr`ete, uniquement bas´ee sur un compilateur : chaque phrase entr´ee par l’utilisateur est aussitˆot compil´ee, puis le code produit est ex´ecut´e « `a la vol´ee ». Cette m´ethode est techniquement plus difficile que l’interpr´etation, mais elle offre de grands avantages : lorsqu’un langage est ´evalu´e par deux m´ethodes diff´erentes, interpr´etation et compilation, il se pose imm´ediatement des probl`emes de coh´erence entre ces deux m´ethodes. Il faut en effet prouver que dans tous les cas les r´esultats produits par l’interpr`ete et par le compilateur sont les mˆemes. En ce qui concerne Caml cette propri´et´e est assur´ee de facto, puisqu’il n’y a qu’un seul moyen d’attribuer une s´emantique au programme : qu’on soit en programmation s´epar´ee ou en interaction directe avec le langage, c’est toujours le mˆeme compilateur qui travaille. Pour la mˆeme raison, il n’y a pas de diff´erence d’efficacit´e entre programmes ind´ependants et proLa compilation de Caml 367 grammes du syst`eme interactif : compil´es comme les autres, les programmes d´evelopp´es interactivement s’ex´ecutent forc´ement `a la mˆeme vitesse. 19.2 La compilation de Caml En point d’orgue `a ce livre, nous aurions naturellement aim´e vous pr´esenter un compilateur pour le langage mini-Caml, produisant du code pour la pico-machine. Nous y avons renonc´e pour des questions de volume : un tel compilateur est un assez gros programme ; mˆeme si la plupart des techniques introduites dans le compilateur mini-Pascal s’appliquent sans changements `a mini-Caml, il reste `a r´esoudre un certain nombre de difficult´es propres `a Caml. La quarantaine de pages de code et d’explications n´ecessaires auraient rendu ce livre trop ´epais. Dans cette conclusion, nous nous contenterons de donner un aper¸cu des probl`emes nouveaux que pose la compilation de Caml et des techniques mises en oeuvre dans les syst`emes Caml. La gestion automatique de la m´emoire Allocation de m´emoire Le langage Caml n´ecessite des m´ethodes complexes de gestion de la m´emoire, c’est-`a-dire d’allocation et de lib´eration des adresses m´emoires. L’allocation consiste `a r´eserver un certain espace dans la m´emoire (un bloc de m´emoire) pour y ranger des donn´ees. On l’utilise par exemple pour fabriquer des tableaux, des paires, des cellules de listes ou des chaˆınes de caract`eres. Un programme Caml ordinaire alloue un grand nombre de blocs, mais ces blocs ont g´en´eralement une dur´ee de vie assez br`eve. Un exemple simple : lorsqu’on utilise des listes, il est courant de cr´eer une liste pour y appliquer aussitˆot map ; en ce cas, la liste de d´epart est devenue inutile, car seule la liste r´esultat est utilis´ee dans le reste du programme. Cela signifie qu’on remplit facilement toute la m´emoire avec des blocs dont beaucoup ne servent plus. Pour ´eviter cela, il faut savoir lib´erer les blocs m´emoire devenus inutiles, pour pouvoir r´eutiliser la place m´emoire qu’ils occupaient. R´ecup´eration de m´emoire Il y a principalement deux moyens de lib´erer des blocs : le premier consiste simplement `a laisser ce soin au programmeur, qui devra explicitement signaler quand un bloc allou´e est libre. Le second est la lib´eration implicite, g´er´ee automatiquement par un programme sp´ecialis´e, le r´ecup´erateur de m´emoire. La lib´eration explicite n’existe pas en Caml, car c’est une source d’erreurs subtiles et fr´equentes. Fr´equentes, parce qu’il est facile d’oublier qu’une partie d’une structure de donn´ees est utilis´ee par la suite et donc de lib´erer trop tˆot cette structure. Subtiles, car lorsqu’un bloc m´emoire est lib´er´e, les donn´ees qu’il contenait ne sont pas imm´ediatement d´etruites : elles resteront valides tant qu’on n’´ecrira pas d’autres donn´ees au mˆeme endroit. Cela signifie que le programme continuera `a marcher un certain temps apr`es la lib´eration qui cr´ee l’erreur. Au gr´e du chemin pris dans le programme, ces valeurs seront d´etruites plus ou moins tard, donnant `a l’utilisateur l’impression que son programme se comporte de mani`ere erratique. 368 En guise de conclusion La r´ecup´eration automatique de m´emoire La manipulation sˆure et facile des structures de donn´ees suppose donc l’allocation et la lib´eration automatique des blocs de m´emoire. Les programmes Caml s’ex´ecutent donc en collaboration avec un programme sp´ecialis´e pour g´erer la m´emoire de la machine : le gestionnaire m´emoire. Ce programme se compose de deux parties, l’allocateur de m´emoire et le r´ecup´erateur de la m´emoire inutilis´ee. Le r´ecup´erateur est commun´ement appel´e «GC», pour garbage col- lector, litt´eralement « ´eboueur ». On traduit g´en´eralement GC par « glaneur de cellules » ou encore « ramasse-miettes ». Le m´ecanisme g´en´eral du GC est le suivant : lorsque l’allocateur de m´emoire ne peut satisfaire une requˆete par manque de m´emoire libre, le GC se d´eclenche et parcourt r´ecursivement toutes les donn´ees utilis´ees par le programme en cours. Il commence par le contenu des registres, de la pile et de toutes les variables globales, puis « descend » r´ecursivement dans les structures de donn´ees. De ce parcours, le GC d´eduit l’ensemble des adresses m´emoire accessibles, donc potentiellement utilis´ees par le programme. Toutes les autres adresses sont forc´ement inutilis´ees et donc r´ecup´erables. Ce m´ecanisme de parcours des donn´ees actives impose des contraintes sur la repr´esentation des structures de donn´ees en m´emoire. Essentiellement, le GC doit savoir distinguer, parmi les champs d’une structure, les pointeurs vers des sousstructures (qu’il faut parcourir r´ecursivement) des donn´ees qui ne sont pas des pointeurs, comme par exemple les nombres entiers (sur lesquels le GC doit arrˆeter son parcours). L’approche suivie par les syst`emes Caml est de coder pointeurs et entiers de mani`ere `a les distinguer par examen de leur code. Par exemple, on code les adresses m´emoires par des mots pairs et les entiers par des mots impairs. Le GC « sait » alors qu’il doit parcourir r´ecursivement les mots pairs et ignorer les mots impairs. Toutes les donn´ees non enti`eres sont repr´esent´ees par l’adresse m´emoire d’un bloc de m´emoire allou´e, bloc qui est muni d’un en-tˆete indiquant au GC la taille du bloc et le type de donn´ees contenues dans le bloc. Les adresses sont naturellement des mots pairs sur la plupart des machines. Quant aux entiers, pour garantir qu’ils sont toujours repr´esent´es par des mots impairs, on repr´esente l’entier Caml n par le mot 2n+1 dans la machine. On compile alors sans difficult´es les primitives arithm´etiques en tenant compte de ce codage (l’addition de deux entiers consiste `a additionner leurs codes et `a soustraire 1 au r´esultat, par exemple). Le prix de ce codage est un l´eger ralentissement des op´erations arithm´etiques et l’impossibilit´e d’obtenir tout l’intervalle des entiers repr´esentables par un mot machine (on perd un bit). La compilation du polymorphisme Tout comme la r´ecup´eration automatique de m´emoire, le polymorphisme impose ´egalement des contraintes sur la repr´esentation des donn´ees Caml dans la machine. Les fonctions polymorphes sont appliqu´ees `a des donn´ees de types diff´erents sur lesquelles ces fonctions op`erent de fa¸con uniforme. Le mˆeme code machine doit donc op´erer uniform ´ement sur des donn´ees de tous les types, des entiers aux structures de donn´ees les plus complexes. Ceci n’est possible que si tous les types de donn´ees partagent un format commun de repr´esentation ; en particulier, si toutes les repr´esentations ont la mˆeme taille. En g´en´eral on choisit une taille d’un mot m´emoire. Les objets qui occupent naturellement plus d’un mot (nombres flottants, n-uplets, etc.) sont alors allou´es La compilation de Caml 369 en m´emoire et manipul´es par l’interm´ediaire d’un pointeur. Cette approche simple se prˆete parfaitement au polymorphisme, au prix d’une certaine inefficacit´e dans les manipulations d’objets allou´es. Pour essayer de r´eduire cette inefficacit´e, une autre voie prometteuse a ´et´e propos´ee r´ecemment pour admettre des donn´ees de taille h´et´erog`ene (comme les tableaux allou´es `a plat de mini-Pascal), tout en conservant le polymorphisme param´etrique de Caml : il s’agit de modifier la repr´esentation des donn´ees au cours de l’ex´ecution des programmes. Ainsi, certaines fonctions non polymorphes travaillent avec des donn´ees occupant plus d’un mot (par exemple des flottants sur deux mots), tandis que les fonctions polymorphes travaillent syst´ematiquement avec des donn´ees d’un mot. Lorsque des fonctions polymorphes et des fonctions normales ´echangent des donn´ees, celles-ci sont allou´ees dans un objet structur´e `a l’entr´ee des fonctions polymorphes qui ne connaissent pas la repr´esentation sp´ecifique des donn´ees ; ainsi les fonctions polymorphes re¸coivent toujours un objet de taille fixe, entier ou pointeur sur leur argument. Sym´etriquement, `a la sortie des fonctions polymorphes, les donn´ees sont extraites de l’objet structur´e qui les contient et remises `a plat pour ˆetre pass´ees aux fonctions monomorphes qui connaissent leur repr´esentation. Il va sans dire que cette m´ethode est bien plus complexe que la pr´ec´edente, mais donne g´en´eralement de meilleurs r´esultats. Actuellement, tous les syst`emes Caml fonctionnent avec des donn´ees de taille uniforme. La compilation de la fonctionnalit´e Les fermetures Au niveau du compilateur proprement dit, la principale nouveaut´e de Caml par rapport `a Pascal est la pleine fonctionnalit´e. Lorsque les fonctions peuvent ˆetre calcul´ees et renvoy´ees en r´esultat, il n’est plus possible de les repr´esenter uniquement par des adresses de morceaux de code machine. Comme nous l’avons vu dans l’interpr´eteur mini-Caml, il faut introduire la notion de fermeture, c’est-`a-dire transformer les fonctions en structures de donn´ees allou´ees contenant, en plus de l’adresse du code de la fonction, l’environnement au moment de la d´efinition de la fonction. Le code produit pour une d´efinition de fonction alloue cette fermeture et y stocke l’environnement courant. Le code produit pour appliquer une fonction va chercher dans la fermeture l’adresse du code de la fonction et se branche `a cette adresse, non sans avoir pass´e la partie environnement de la fermeture en argument suppl´ementaire `a la fonction. Le corps de la fonction est compil´e de mani`ere `a aller chercher dans cet argument suppl´ementaire la valeur des identificateurs libres. Appel de fonctions inconnues En plus du recours aux fermetures, la pleine fonctionnalit ´e impose un m´ecanisme uniforme d’appel de fonctions. Toute fonction Caml est susceptible d’ˆetre appel´ee par une fonction qui ne connaˆıt rien de la fonction qu’elle appelle : par exemple, map re¸coit n’importe quelle fonction en argument et l’appelle sans rien savoir `a son propos. En g´en´eral, lorsque le compilateur produit du code pour une application de fonction, il ne connaˆıt pas la d´efinition de cette fonction, ce qui l’empˆeche de faire certaines optimisations simples. En particulier, une application de fonction `a plusieurs arguments f e1 . . . en n’est g´en´eralement pas compilable « `a la Pascal », en passant les n arguments d’un seul coup, car rien ne garantit que f soit une fonction `a n arguments de la forme f x1 . . . xn = . . . : elle pourrait aussi bien ˆetre le r´esultat 370 En guise de conclusion d’un calcul beaucoup plus compliqu´e, rendant n´ecessaire de passer les n arguments un par un, avec des constructions de fermetures interm´ediaires (pensez `a map successeur, pour une fonction `a un argument, et `a let f x = let ... in (function y -> ...), pour une fonction `a deux arguments). En bref, la notion d’arit´e d’une fonction est difficile `a d´efinir en Caml : elle ne se d´etecte pas directement par le type des fonctions et impose donc une analyse parall`ele au typage. La difficult´e est encore plus grande si l’on souhaite traiter de la mˆeme mani`ere les fonctions n-aires curryfi´ees et les fonctions n-aires non curryfi´ees (celles dont les arguments sont syntaxiquement sous la forme d’un n-uplet). Les meilleurs compilateurs Caml savent optimiser les appels directs aux deux types de fonctions. Cr´eation des fermetures En pratique, beaucoup de programmes Caml n’utilisent pas la pleine fonctionnalit´e et sont en fait tr`es proches de programmes Pascal (tout au moins du point de vue de la compilation). Si le compilateur s’efforce d’adopter pour ces programmes la mˆeme strat´egie qu’un compilateur Pascal, il fera du bon travail. Par exemple, dans les programmes courants, la plupart des appels de fonctions concernent des fonctions connues du compilateur et dans ce cas le compilateur produit du code plus efficace, en passant tous les arguments d’un seul coup et en engendrant un appel direct au code de la fonction. De la mˆeme fa¸con, on n’est pas oblig´e de fabriquer syst´ematiquement une fermeture pour toutes les fonctions d’un programme car beaucoup de fonctions restent locales au module (ou `a la phrase) qui les d´efinit. Le compilateur doit s’efforcer de d´etecter ces cas. Remarquez cependant qu’on ne peut pas ´eliminer la fabrication dynamique de fermetures, au cours de l’ex´ecution (pensez `a let g = function x -> function y -> x + y, puis `a let h = g 3, ou encore `a des fonctions dont la valeur est ´ecrite dans une structure de donn´ees). Lorsqu’il est contraint et forc´e d’allouer une fermeture, le compilateur a le choix entre plusieurs strat´egies d’allocation des environnements de fermetures. La premi`ere est le partage maximal des environnements : l’environnement d’ex´ecution du programme est constamment maintenu `a l’ex´ecution, comme pour notre interpr´eteur mini-Caml, et l’allocation d’une fermeture se r´eduit `a cr´eer une paire entre l’environnement courant et l’adresse de code de la fonction. L’autre strat´egie est l’allocation « `a plat » des fermetures. Le compilateur cr´ee un tableau contenant les valeurs des variables libres du corps de la fonction. Cette m´ethode assure que l’environnement de la fermeture contient seulement les valeurs n´ecessaires `a l’ex´ecution de la fonction, mais elle oblige `a recopier ces valeurs `a chaque cr´eation de fermeture. La premi`ere m´ethode partage au maximum les environnements, mais tout l’environnement d’ex´ecution est mis dans la fermeture cr´e´ee. Cette m´ethode met donc aussi le maximum de valeurs inutiles dans les fermetures, occasionnant ainsi des fuites de m´emoires, ce qui correspond `a la r´etention de cases m´emoires non utilis´ees mais irr´ecup´erables par le GC, car toujours accessibles `a partir des donn´ees du programme (dans notre cas, une fermeture qui contient une donn´ee `a laquelle elle n’acc´edera jamais). Ces fuites de m´emoires s’av`erent r´edhibitoires pour certains programmes, dans la mesure o`u elles sont imparables : le programmeur n’a pas les moyens de les ´eviter, puisque c’est la m´ethode de compilation des programmes qui les engendre. C’est pourquoi nous pr´ef´erons l’allocation `a plat, qui tient un plus juste compte des objets r´eellement indispensables `a l’ex´ecution. La compilation de Caml 371 Le socle du compilateur En g´en´eralisant la discussion ci-dessus, il apparaˆıt deux approches radicalement diff´erentes de la compilation de Caml. L’une consiste `a s’appuyer sur un mod`ele d’ex´ecution int´egrant la pleine fonctionnalit´e (dans lequel les fonctions peuvent avoir des variables libres). Ce mod`ele d’ex´ecution est souvent bas´e sur une machine virtuelle pour l’ex´ecution du -calcul. Les seules optimisations que le compilateur peut alors effectuer sont des transformations de programmes de haut niveau ; l’optimisation des fonctions elles-mˆemes et de leur repr´esentation sous forme de fermetures est difficilement exprimable. L’autre approche consiste `a exposer beaucoup plus tˆot la repr´esentation des fonctions par des fermetures. On se ram`ene ainsi `a un langage interm´ediaire de type langage algorithmique classique, souvent proche du langage C, sur lequel le compilateur peut appliquer de nombreuses optimisations de bas niveau. Cette derni`ere approche, quoique plus complexe, donne g´en´eralement de meilleurs r´esultats : les compilateurs obtenus par cette approche compilent bien ce qu’il est facile de bien compiler (appels `a des fonctions connues, fonctions sans variables libres), ce qui repr´esente une large part des programmes qu’on ´ecrit en Caml. Le principal ´ecueil qui menace cette approche est, `a force d’am´eliorer les cas simples, de trop n´egliger les cas compliqu´es et d’aboutir `a une compilation incorrecte de ces cas difficiles. L’auto-g´en´eration Quoi qu’il en soit, vous devez entrevoir maintenant les ´etapes principales d’une impl´ementation compl`ete de Caml en Caml : description de la syntaxe abstraite, de la syntaxe concr`ete, du typage, et enfin de la compilation. Ce m´ecanisme d’autodescription est g´en´eral dans les syst`emes Caml : il sont tous autog`enes, c’est-`a-dire produits par eux-mˆemes. En effet, le compilateur est enti`erement ´ecrit en Caml, c’est donc un programme Caml comme tous les autres, compilable par le compilateur Caml, c’est- `a-dire par lui-mˆeme. Ce m´ecanisme d’auto-amor¸cage ou d’auto-g´en´eration s’appelle bootstrap en anglais. L’auto-g´en´eration est un m´ecanisme ´etrange, puisqu’il s’agit d’une sorte de d´efinition r´ecursive au niveau de la sp´ecification ex´ecutable du compilateur du langage. On se demande bien par quel miracle il en sort un syst`eme Caml qui tourne. C’est simplement que la r´ecursion s’arrˆete sur le compilateur « de niveau z´ero », le compilateur initial, qui est toujours ´ecrit dans un autre langage. Progressivement, certaines parties du compilateur initial sont r´e´ecrites dans le langage compilable par le compilateur, jusqu’`a obtenir finalement un compilateur enti`erement ´ecrit dans le langage du compilateur : le langage est devenu autog`ene. Au-del`a du tour de force qu’elle constitue, l’auto-g´en´eration est un gage de qualit´e pour un compilateur : le programme qui r´ealise la compilation est forc´ement complexe et long, c’est un bon crit`ere de savoir que le compilateur est capable de le compiler sans erreurs, en produisant un code acceptable en un temps raisonnable. C’est une ´etape majeure dans la validation d’un langage de programmation et de son impl´ementation. Rappelons encore une fois que tous les syst`emes Caml sont autog`enes. L’auto-g´en´eration d’un compilateur Caml serait donc une belle aventure `a raconter, «mais ceci est une autre histoire . . . » l l l l l l l l l l l l l l l l l l Index ´echec, 77, 126 ´ecriture d’une r´ef´erence, 48 ´ecriture dans un enregistrement, 119, 150 ´ecriture dans un tableau, 41 ´ecriture dans une chaˆıne, 46 ´ecriture sur fichier, 182 ´edition de liens, 187 ´egalit´e, 43, 121, 316 ´egalit´e physique, 354 ´enum´er´es (types), 113 ´equivalence, 211 ´etiquette, 117 ´etiquettes (d’assemblage), 267 ´etoile, 42 ´evaluation paresseuse, 162, 334 !, 47 !=, 354 "...", 5 #, 6, 148 #open, 184 &&, 26, 214 (), 14 (*, 10 *, 63 *), 10 *., 149 +., 149 ->, 11, 28, 63–65 ., 117, 149 .(, 41 .[, 24, 46, 133, 164 :, 11 ::, 75 :=, 48 ;, 14 ;;, 6 <-, 41, 46, 119, 150, 164 <>, 43 =, 214, 316 ==, 354 @, 92, 102 [...], 75 [<...>], 162, 163, 165 [], 75 [|...|], 40 ^, 8, 68, 88, 100 _, 28, 85 ‘...‘, 24 {...}, 116 |, 28, 110, 135, 163 ||, 26, 214 ’_a, 87, 364 ’, 162 ’a, 58 abr´eviation de type, 261 abstraction, 66, 81, 91 acc`es dans un tableau, 41 acc`es direct dans les fichiers, 246 accumulateur, 47, 49, 61, 102, 216 affectation, 37, 41, 48, 54 al´eatoires (nombres), 140, 201 allocation m´emoire, 290, 367 alternative, 12, 26, 43 analyse de cas, 28 analyse lexicale, 160, 162, 217, 228 analyse syntaxique, 159, 160, 166, 173, 218, 271, 279, 308, 336, 365 and, 8 appel par n´ecessit´e, 334 374 Index appel par nom, 334 appel par valeur, 334 appels syst`eme (du pico-processeur), 265 application, 9, 16, 73 application partielle, 64, 67, 221, 231 arbres de Huffman, 242 arbres tournoi, 248 argument n´egatif, 15 arguments d’une commande, 240 as, 80, 117 ASCII (code), 133, 163 assembleur, 267 associativit´e, 16 auto-g´en´eration, 371 automates, 309 automates d´eterministes, 313 begin, 14 belote, 115 bien fond´ee (r´ecursion), 19, 22 bind, 197 bit `a bit (op´erations), 250 bloc d’activation, 291 BNF, 17 bool´eens, 13, 26 bootstrap, 371 bouclage, 8 boucle d’interaction, 142, 183, 193, 222, 332, 358 boucles, 39, 41, 49, 51, 61, 68, 101, 129 bouton, 193 cadre, 194 Cam´elia, 125 CamlTk, 193 canaux d’entr´ee-sortie, 182, 183 caract´eristique, 117 caract`eres, 24, 46 cartes, 115 cas inutiles dans le filtrage, 120 chaˆınes de caract`eres, 24, 25, 46, 66, 88, 129, 133 champ, 117 chargement de fichiers, 179 circulaires (types), 354 codage de Huffman, 241, 242, 245 commandes, 38 commentaires, 10, 229 compilateur ind´ependant, 6, 180 compilation, 181, 186, 188, 190, 222, 289, 366 complexit´e, 31, 33, 35, 81, 98 composition de fonctions, 70, 96 compression, 237, 241 compteur, 47, 48, 54 concat´enation de chaˆınes, 8, 68, 88, 100 concat´enation de listes, 84, 92, 97, 102 conditionnelle, 12, 26, 43, 84 configure, 195 conjonction, 26, 209 connecteurs propositionnels, 208 cons, 75 constructeurs de listes, 75 constructeurs de types, 63 constructeurs de valeurs, 110, 116 contrainte de type, 11, 62 contraintes de type, 11 conventions syntaxiques, 15 cosinus, 150 couleur de trac´e, 152 crayon, 149, 155 curryfication, 64, 72 d´ecalage logique, 251, 252, 263 d´eclarations, 187 d´eclenchement d’exceptions, 127, 172 d´ecompression, 242 d´efinition, 9 d´efinition par cas, 28 d´efinitions, 6, 8, 20 d´er´ef´erencement, 47 d´eterminisation, 313 diagrammes syntaxiques, 17, 39, 42, 84, 129 disjonction, 26, 209 do, 39 done, 39 downto, 39 effacement d’un fichier, 239 effet, 13, 37, 53, 54 else, 12 375 end, 14 enregistrements, 116, 150 ensembles, 316, 320 entr´ee standard, 183 entr´ees-sorties, 181, 238, 334 entr´ees-sorties structur´ees, 246, 265, 274 environnements d’´evaluation, 170, 172, 329 environnements de typage, 284 erreurs, 126 Ershov, 295 et (bool´een), 26 et bit `a bit, 252, 263 exception, 128 exceptions, 77, 126, 127, 129, 172, 215, 334 exn, 127, 128 exponentielle (complexit´e), 32, 360 expressions, 168 expressions rationnelles, 305 extensionnalit´e, 52 factorielle, 20, 49, 268, 275 factorisation `a gauche, 309 false, 13 fermeture d’un canal, 182 fermetures, 327, 329, 334, 369 Fibonacci, 33, 277, 303 fichiers, 182 file d’attente, 245, 247 filtrage, 28, 76, 78, 84, 85, 110, 117, 120, 126, 128, 158, 162, 165, 328 filtrage exhaustif, 122 filtrage partiel, 122 filtres « ou », 135 fin de fichier, 182 fin de phrase, 6 fl`eche (type fonctionnel), 9 float_of_string, 196 flocon, 147, 154 flottants (nombres), 34, 149 flux, 161, 165, 183 fonctionnelle (programmation), 37, 268 fonctionnelles, 59–61, 64, 66, 70, 72, 73, 81, 220, 279 fonctionnelles sur les listes, 81, 91, 94, 96 fonctions, 8–10, 67, 68, 342 fonctions anonymes, 11, 68, 343 fonctions calcul´ees, 60 for, 39 fractales, 147 function, 11 g´en´eralisation, 345, 356 gardes dans les filtres, 121 GC, 367 glissi`ere, 194 graphisme, 148 hachage, 229 Hanoi (les tours de), 28, 88 heaps, 248 Huffman, 240 identificateurs, 7 identit´e, 58, 62 if, 12 imp´erative (programmation), 38, 101, 268 impl´ementations de modules, 187 implication, 210 implose, 92, 99–101 impression `a l’´ecran, 22, 39, 67, 140, 182 impression format´ee, 195, 196, 293 in, 8 incr´ementation, 48 inf´erence de types, 6, 61, 339 instructions (du pico-processeur), 256 int_of_float, 195 interactivit´e, 6, 181, 366 interface homme-machine, 193 interfaces, 221 interfaces de modules, 186–188 interpr´etation, 326, 366 interruption, 142 invite, 6 it´erateurs, 81, 82, 92, 94, 96, 105 lecture au clavier, 67, 141, 182 lecture d’une r´ef´erence, 47 lecture dans un tableau, 41 lecture sur fichier, 182 Lempel-Ziv, 253 let, 7, 8 376 Index let rec, 21, 22 lex`emes, 160 liaisons, 7, 20 lin´eaire (complexit´e), 32, 101 lin´earit´e du filtrage, 121 listes, 75–78, 83, 88, 130 listes d’association, 131, 134, 170, 230 logique, 207 Logo, 147, 169 longueur d’un tableau, 41, 42 longueur d’une chaˆıne, 24, 46, 100 longueur d’une liste, 93, 104 mainLoop, 194 make_matrix, 199 match, 84 matrice, 199 menus, 197 modifications physiques, 38, 150, 163, 363 modules, 184 monomorphe, 57 mot-cl´e, 217 motifs « ou », 163 motifs intervalles, 163 mutable, 150 n-uplets, 63 n´egation, 208 nil, 75 noms, 7 noms ext´erieurs, 184 noms qualifi´es, 184 not, 214 occurrence (test d’), 354 of, 110 open, 184 openTk, 194 ou (bool´een), 26 ou bit `a bit, 251, 263 ou exclusif, 263 ouverts (modules), 184 ouverture d’un canal, 182, 239 pack, 194 paires, 63, 69 palindromes, 25 paradoxes, 208 param`etres de types, 58 Pascal, 277 peintures, 113 phrase Caml, 6 pico-processeur, 255, 289 pile (du pico-processeur), 259, 291 piles, 318 point courant, 148 point fixe, 334 polymorphisme, 57, 66, 70, 71, 76, 81, 340, 342, 368 polynˆomes, 40, 42, 43, 45, 83, 85, 109, 111 port´ee dynamique, 20, 177, 328 port´ee statique, 20, 177, 327 positionnement dans un fichier, 246 prefix, 95 print_char, 24 printf, 293 priorit´e (file d’attente avec), 245, 247 priorit´es, 218, 281, 308 proc´edures, 14, 38, 168 produit (types), 63, 116 programmes ind´ependants, 180 prompt, 6 propositions, 208, 213 quadratique (complexit´e), 32, 81, 101, 102 r´ecup´eration d’exceptions, 126, 128, 142, 172 r´ecup´eration de m´emoire, 367 r´ecurrence, 32, 33, 36, 103, 122 r´ecursifs (types), 114 r´ecursion, 19, 22, 27, 28, 30, 32, 77, 154, 175, 177, 330, 334, 348 r´ef´erences, 47, 90, 101, 119, 230 r`egle , 52, 54, 55, 95, 363 random, 201 rec, 22 records, 116 red´efinition, 48 ref, 47 registres (allocation de), 294, 298 registres (du pico-processeur), 256 regular expressions, 305 377 remplissage de rectangles, 152 retard de l’´evaluation, 55, 157 retournement d’une liste, 101 rien (valeur ()), 14 RISC, 255, 258 s´equence, 14, 17, 53, 81 sch´emas de types, 340, 341 simulateur (du pico-processeur), 260 sinus, 150 sommation (d’une formule), 61 somme (types), 110, 116 sortie d’erreur, 183 sortie standard, 183 sous-chaˆıne, 26, 134, 240 sous-programmes, 259 sp´ecialisation, 59, 62, 67, 345, 357 spirales, 175 sprintf, 195, 196 stack frame, 291 streams, 161 suspensions, 334 synonymes (dans les filtres), 80, 117 syntaxe abstraite, 159, 278, 289, 365 syntaxe concr`ete, 159, 365 synth`ese de types, 339, 344 tableaux, 40, 42, 65, 68, 75, 270 terminaison, 8 then, 12 Thompson, 312 Tk, 193 to, 39 trac´e de lignes, 148, 151 trace, 23 tri, 65, 78, 81, 104 trigonom´etrie, 150, 151 true, 13 try, 126, 128 typage, 5, 61, 283 type, 110 unification, 345, 353, 362 unit, 14 v´erit´e (tables de), 209, 210 v´erit´e (valeurs de), 13, 208 value, 187 variables, 7, 48 variables de types, 340, 348 variables libres, 216 variables r´emanentes, 120 vect, 40, 63 vecteurs, 40 vidage des canaux, 183 when, 121 while, 39 with, 84, 126, 128 All technical caracteristics are subject to change without previous notice. Caractéristiques sujettes à modifications sans préavis. Proud to serve you celduc r e l a i s page 1 / 5F/GB S/MON/SO967460/B/01/03/2005 Relais statique monophasé de puissance Power Solid State Relay SO967460 okpac ❏ Relais statique synchrone spécialement adapté aux charges résistives. Zero Cross Solid State Relay specially designed for resistive loads. ❏ Sortie thyristors technologie TMS2 (*) permettant une longue durée de vie : 24 à 600VAC 75A. Back to back thyristors on output with TMS2 (*) technology for a long lifetime expectancy : 24 to 600VAC 75A. ❏ Large plage de contrôle: 3,5 - 32VDC avec un courant de commande régulé. LED de visualisation sur l'entrée de couleur verte. Protection aux surtensions sur l'entrée intégrée. Large control range: 3.5-32VDC with input current limiter. Green LED visualization on the input. Input over-voltage protection. ❏ Protection IP20 sur demande par l'ajout de volets. IP20 protection flaps on request (option). ❏ Construit en conformité aux normes EN60947-4-3 (IEC947-4-3) et EN60950/VDE0805 (Isolement renforcé) -UL-cUL Designed in conformity with EN60947-4-3 (IEC947-4-3) and EN60950/VDE0805 (Reinforced Insulation) -UL-cUL Output : 24-600VAC 75A Input : 3,5-32VDC Typical application: 30kW resistor (AC-51 load) on 400 VAC avec protection IP20/ with IP20 flaps Entrée control + * 1/L1 et 2/T2 peuvent être inversées/ 1/L1 T2 can be changed * le relais doit être monté sur dissipateur thermique / SSR must be mounted on a heatsink 24-600VAC - CHARGE/LOAD protection réseau line protection 4/A2- 3/A1+ 1/L1 2/T1 LED ZC sans protection IP20/ without IP20 flaps (*) : Thermo mechanical Stress Solution - 1/L1 et 2/T1 peuvent être inversées. 1/L1 and 2/T1 can be swapped. - Le relais être monté sur dissipateur thermique. SSR must be mounted on heatsink Application typique: Resistance 30 kW (Catégorie AC-51) en 400 VAC 3,5-32VDC Volets IP20 sur demande/ with IP20 flaps on request Dimensions : r e l a i s Rue Ampère B.P. 4 42290 SORBIERS - FRANCE E-Mail : celduc-relais@celduc.com Fax +33 (0) 4 77 53 85 51 Service Commercial France Tél. : +33 (0) 4 77 53 90 20 Sales Dept.For Europe Tel. : +33 (0) 4 77 53 90 21 Sales Dept. Asia : Tél. +33 (0) 4 77 53 90 19 www.celduc.com celduc Caractéristiques d'entrée / Control characteristics (at 25°C) DC Paramètre / Parameter Symbol Min Typ Max Unit Tension de commande / Control voltage Uc 3,5 5-12-24 32 V Courant de commande / Control current (@ Uc ) Ic <10 <13 <13 mA Tension de non fonctionnement / Release voltage Uc off 2 V LED d'entrée / Input LED verte / green Tension Inverse / Reverse voltage Urv 32 V Tension de transil d'entrée / Clamping voltage (Transil) Uclamp 36 V Immunité / Input immunity : EN61000-4-4 2kV Immunité / Input immunity : EN61000-4-5 2KV Caractéristiques de sortie / Output characteristics (at 25°C) Paramètre / Parameter Conditions Symbol Min Typ. Max Unit Plage de tension utilisation / Operating voltage range Ue 24 400 600 V rms Tension de crête / Peak voltage Up 1200 V Niveau de synchronisme / Zero cross level Usync 35 V Tension minimum amorçage / Latching voltage Ie nom Ua 10 V Courant nominal / nominal current (AC-51) Ie AC-51 75 90 A rms Courant surcharge / Non repetitive overload current tp=10ms (Fig. 3) Itsm 1000 1200 A Chute directe à l'état passant / On state voltage drop @ 25°C Vt 0,9 V Résistance dynamique / On state dynamic resistance rt 4,5 mΩ Puissance dissipée (max) / Output power dissipation (max value) Pd 0,9x0,9xIe + 0,0045xIe2 W Résistance thermique jonction/semelle Thermal resistance between junction to case Rthj/c 0,4 K/W Courant de fuite à l'état bloqué / Off state leakage current @Ue typ, 50Hz Ilk 1 mA Courant minimum de charge / Minimum load current Iemin 5 mA Temps de fermeture / Turn on time @Ue typ, 50Hz ton max 10 ms Temps d'ouverture / Turn off time @Ue typ, 50Hz toff max 10 ms Fréquence utilisation/ Operating frequency range F mains f 0,1 50-60 800 Hz dv/dt à l'état bloqué / Off state dv/dt dv/dt 500 V/μs di/dt max / Maximum di/dt non repetitive di/dt 50 A/μs I2t (<10ms) I2t 5000 7200 A2s Immunité / Conducted immunity level IEC/EN61000-4-4 (bursts) 2kV criterion B Immunité / Conducted immunity level IEC/EN61000-4-5 (surge) 2kV criterion A with external VDR Protection court-circuit / Short circuit protection voir/see page 5 Example Fuse Ferraz URC63A or fast Breaker Input : Ic = f( Uc) page 2 / 5F/GB S/MON/SO967460/B/01/03/2005 okpac Caractéristiques générales / General characteristics (at 25°C) Symbol Isolement entrée/sortie - Input to output insulation Ui 4000 VRMS Isolation sortie/ semelle - Output to case insulation Ui 4000 VRMS Résistance Isolement / Insulation resistance Ri 1000 (@500VDC) MΩ Tenue aux tensions de chocs / Rated impulse voltage Uimp 4000 V Degré de protection / Protection level / CEI529 IP00 Degré de pollution / Pollution degree - 2 Vibrations / Vibration withstand 10 -55 Hz according to CEI68 double amplitude 1,5 mm Tenue aux chocs / Shocks withstand according to CEI68 - 30/50 g Température de fonctionnement / Ambient temperature (no icing, no condensation) - -40 /+100 °C Température de stockage/ Storage temperature (no icing, no condensation) -40/+125 °C Humidité relative / Ambient humidity HR 40 to 85 % Poids/ Weight 80 g Conformité / Conformity EN60947-4-3 (IEC947-4-3) Conformité / Conformity VDE0805/EN60950 UL/cUL plastique du boitier / Housing Material PA 6 UL94VO Semelle / Base plate Aluminium, nickel-plated 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 0 2 4 6 8 10 12 14 Uc (VDc) Ic (mA) r e l a i s Rue Ampère B.P. 4 42290 SORBIERS - FRANCE E-Mail : celduc-relais@celduc.com Fax +33 (0) 4 77 53 85 51 Service Commercial France Tél. : +33 (0) 4 77 53 90 20 Sales Dept.For Europe Tel. : +33 (0) 4 77 53 90 21 Sales Dept. Asia : Tél. +33 (0) 4 77 53 90 19 www.celduc.com celduc page 3 / 5F/GB S/MON/SO967460/B/01/03/2005 okpac Surcharge de courant non répétitive sans tension réappliquée / No repetive surge current without voltage reapplied. Surcharge de courant répétitive avec tension réappliquée Repetive surge current with voltage reapplied. 0,01 0,1 1 10 0 500 1000 1500 t (s) Itsm (Apeak) Surcharge de courant :Itsm (Apeak)=f(t) pour modéle 75A/ Surge current : Itsm (Apeak) = f(t) for 75A Fig.3: 1 2 fig 3 : Courants de surcharges / Overload currents 6K/W correspond à un relais monté sur un adaptateur DIN celduc type 1LD12020 6K/W corresponds to a relay mounted on a DIN rail adaptator like celduc 1LD12020 Fig. 2 Courbes thermiques & Choix dissipateur thermique / Thermal curves and heatsink choice 0 10 20 30 40 50 60 70 80 90 0 10 20 30 40 50 60 70 80 90 100 110 Courant de charge / RMS load current (A) Puissance Dissipée / Power Dissipation (W) 0 10 20 30 40 50 60 70 80 90 100 0 10 20 30 40 50 60 70 80 90 100 110 Température ambiante / Ambient temperature (°C) 1,1K/W 6K/W 2,1K/W 1,5K/W Full on State 50% on State 0,95K/W 0,75K/W 0,55K/W 0,3K/W −> Warning ! semiconductor relays don't provide any galvanic insulation between the load and the mains. Always use in conjunction with an adapted circuit breaker with isolation feature or a similar device in order to ensure a reliable insulation in the event of wrong function and when the relay must be insulated from the mains (maintenance ; if not used for a long duration ...). 1 -Itsm non répétitif sans tension réappliquée est donné pour la détermination des protections. 1 - No repetitive Itsm is given without voltage reapplied . This curve is used to define the protection (fuses). 2 -Itsm répétitif est donné pour des surcharges de courant (Tj initiale=70°C). Attention : la répétition de ces surcharges de courant diminue la durée de vie du relais. 2 - Repetitive Itsm is given for inrush current with initial Tj = 70°C. In normal operation , this curve musn't be exceeded. Be careful, the repetition of the surge current decreases the life expectancy of the SSR. −> Attention ! les relais à semi-conducteurs ne procurent pas d'isolation galvanique entre le réseau et la charge. Ils doivent être utilisés associés à un disjoncteur avec propriété de sectionnement ou similaire, afin d'assurer un sectionnement fiable en amont de la ligne dans l'hypothèse d'une défaillance et pour tous les cas où le relais doit être isolé du réseau (maintenance ; non utilisation sur une longue durée...). r e l a i s Rue Ampère B.P. 4 42290 SORBIERS - FRANCE E-Mail : celduc-relais@celduc.com Fax +33 (0) 4 77 53 85 51 Service Commercial France Tél. : +33 (0) 4 77 53 90 20 Sales Dept.For Europe Tel. : +33 (0) 4 77 53 90 21 Sales Dept. Asia : Tél. +33 (0) 4 77 53 90 19 www.celduc.com page 4 / 5F/GB S/MON/SO967460/B/01/03/2005 okpac okpac Raccordement d'entrée / Control wiring Nombre de fils / Number of wires Modèle de tournevis / Screwdriver type Couple de serrage recommandé 1 2 Recommended Torque Fil rigide (sans embout) SOLID (No ferrule) Fil multibrins (avec embout) FINE STRANDED (With ferrule) Fil rigide (sans embout) SOLID (No ferrule) Fil multibrins (avec embout) FINE STRANDED (With ferrule) M4 N.m 0,75 ... 2,5 mm2 AWG18....AWG14 0,75 ... 2,5 mm2 AWG18....AWG14 0,75 ... 2,5 mm2 AWG18....AWG14 0,75 ... 2,5 mm2 AWG18....AWG14 POZIDRIV 2 1,2 okpac Raccordement de puissance / Power wiring Nombre de fils / Number of wires Modèle de tournevis / Screwdriver type Couple de serrage recommandé 1 2 Recommended Torque Fil rigide (sans embout) SOLID (No ferrule) Fil multibrins (avec embout) FINE STRANDED (With ferrule) Fil rigide (sans embout) SOLID (No ferrule) Fil multibrins (avec embout) FINE STRANDED (With ferrule) M5 N.m 1,5 ... 10 mm2 AWG16....AWG8 1,5 ... 6 mm2 AWG16....AWG10 1,5 ... 10 mm2 AWG16....AWG8 1,5 ... 6 mm2 AWG16....AWG10 POZIDRIV 2 2 Raccordement / Connections celduc Options : Volets IP20 1K453000 = référence d'un volet sans le montage 1LK00500 = 1 volet (côté puissance) + 1 montage celduc 1LK00600 = 2 volets (puissance & commande) + montages celduc Option : IP20 flaps 1K453000 : Flap reference without mounting 1LK00500 = 1 flap (on output) + 1 celduc mounting 1LK00600 = 2 flaps (input & output) + 2 celduc mounting FASTONS : Nous consulter / Consult us Directement avec fils avec ou sans embouts/ Direct connection with wires with or without ferrules Avec cosses/ With ring terminals Puissance avec cosses / Power with ring terminals. W max =12,6mm 16 mm2 (AWG6) 25 mm2 (AWG4) 35mm2 (AWG2 /AWG3) 50mm2 (AWG0 /AWG1) Des cosses et kits d'adaptation peuvent être fournis : voir relais forte puissance et documentation connexion forte puissance/ Suitable ring terminals and special kit for high current can be delivered: see high power SSR and data-sheet for power connexion. r e l a i s Rue Ampère B.P. 4 42290 SORBIERS - FRANCE E-Mail : celduc-relais@celduc.com Fax +33 (0) 4 77 53 85 51 Service Commercial France Tél. : +33 (0) 4 77 53 90 20 Sales Dept.For Europe Tel. : +33 (0) 4 77 53 90 21 Sales Dept. Asia : Tél. +33 (0) 4 77 53 90 19 www.celduc.com S/MON/SO967460/B/01/03/2005 okpac Montage /Mounting: −> Les relais statiques de la gamme okpac doivent être montés sur dissipateur thermique. Une gamme étendue de dissipateurs est disponible. Voir exemples ci dessous et la gamme "WF" sur www.celduc.com. okpac SSRs must be mounted on heatsinks. A large range of heatsinks is available. See below some examples and "WF" range on www.celduc.com. −> Pour le montage du relais sur dissipateur utiliser de la graisse thermique ou un "thermal pad" haute performance spécifié par celduc .Une version autocollante précollée sur le relais est aussi disponible: nous consulter / For heatsink mounting, it is necessary to use thermal grease or thermal pad with high conductibility specified by celduc. An adhesive model mounted by celduc on the SSR is also available: please contact us. Application typiques / Typical LOADS −> Le produit SO9 est défini principalement pour charge résistive AC-51 (chauffage). Pour les autres charges, consulter notre guide de choix. SO9 product is specially designed for AC-51 résistive load (heating). For other loads, consult our selection guide Protection /Protection : −> La protection d'un relais statique contre les court-circuits de la charge peut être faite par fusibles rapides avec des I2t = 1/2 I2t du relais . Un test en laboratoire a été effectué sur les fusibles de marque FERRAZ. Une protection par MCB ( disjoncteurs modulaires miniatures) est aussi possible. Voir notre note application ( protection SSR) et utiliser des relais avec I2t >5000A2s To protect the SSR against a short-circuit of the load , use a fuse with a I2t value = 1/2 I2t value specified page 2. A test has been made with FERRAZ fuses . It is possible to protect SSR by MCB ( miniature circuit breaker). In this case, see application note ( SSR protection) and use a SSR with high I2t value (5000A2s minimum). EMC : −> Immunité : Nous spécifions dans nos notices le niveau d'immunité de nos produits selon les normes essentielles pour ce type de produit, c'est à dire EN61000-4-4 &5. Immunity : We give in our data-sheets the immunity level of our SSRs according to the main standards for these products: EN61000-4-4 &5. −> Emission: Nos relais statiques sont principalement conçus et conformes pour la classe d'appareils A (Industrie). L'utilisation du produit dans des environnements domestiques peut amener l'utilisateur à employer des moyens d'atténuation supplémentaires. En effet, les relais statiques sont des dispositifs complexes qui doivent être interconnectés avec d'autres materiels (charges, cables, etc) pour former un système. Etant donné que les autres materiels ou interconnexions ne sont pas de la responsabilité de celduc, il est de la responsabilité du réalisateur du système de s'assurer que les systèmes contenant des relais statiques satisfont aux prescriptions de toutes les règles et règlements applicables au niveau des systèmes. Consulter celduc qui peut vous conseiller ou réaliser des essais dans son laboratoire sur votre application. Emission: celduc SSRs are mainly designed in compliance with standards for class A equipment (Industry). Use of this product in domestic environments may cause radio interference. In this case the user may be required to employ additionnal devices to reduce noise. SSRs are complex devices that must be interconnected with other equipment (loads, cables, etc.) to form a system. Because the other equipment or the interconnections may not be under the control of celduc, it shall be the responsability of the system integrator to ensure that systems containing SSRs comply with the requirement of any rules and regulations applicable at the system level. Consult celduc for advices. Tests can be preformed in our laboratory. Thermal pad : 5TH21000 WF151200 (2-2,5 K/W) WF108110 ( 1,1 K/W) Installation des volets IP20 / IP20 flaps mounting Poussez et fermer. Push and close M4x12mm 1,2N.m Thermal grease or pad page 5 / 5F/GB celduc 1 2 3 4 5 6 7 8 9 18 17 16 15 14 13 12 11 10 1B 2B 3B 4B 5B 6B 7B 8B GND 1C 2C 3C 4C 5C 6C 7C 8C COM DW OR N PACKAGE (TOP VIEW) ULN2803A www.ti.com SLRS049F –FEBRUARY 1997–REVISED JANUARY 2014 ULN2803A Darlington Transistor Arrays Check for Samples: ULN2803A 1FEATURES DESCRIPTION • 500-mA-Rated Collector Current The ULN2803A device is a high-voltage, high-current (Single Output) Darlington transistor array. The device consists of eight npn Darlington pairs that feature high-voltage • High-Voltage Outputs: 50 V outputs with common-cathode clamp diodes for • Output Clamp Diodes switching inductive loads. The collector-current rating • Inputs Compatible With Various of each Darlington pair is 500 mA. The Darlington Types of Logic pairs may be connected in parallel for higher current capability. • Relay-Driver Applications • Compatible with ULN2800A Series Applications include relay drivers, hammer drivers, lamp drivers, display drivers (LED and gas discharge), line drivers, and logic buffers. The ULN2803A device has a 2.7-kΩ series base resistor for each Darlington pair for operation directly with TTL or 5-V CMOS devices. 1 Please be aware that an important notice concerning availability, standard warranty, and use in critical applications of Texas Instruments semiconductor products and disclaimers thereto appears at the end of this data sheet. PRODUCTION DATA information is current as of publication date. Copyright © 1997–2014, Texas Instruments Incorporated Products conform to specifications per the terms of the Texas Instruments standard warranty. Production processing does not necessarily include testing of all parameters. 2.7 k! 7.2 k! 3 k! COM Output C E Input B 8C 7C 6C 5C 4C 3C 2C 7 6 5 4 3 2 1 7B 6B 5B 4B 3B 2B 1B 11 12 13 14 15 16 17 COM 8 8B 10 1C 18 ULN2803A SLRS049F –FEBRUARY 1997–REVISED JANUARY 2014 www.ti.com This integrated circuit can be damaged by ESD. Texas Instruments recommends that all integrated circuits be handled with appropriate precautions. Failure to observe proper handling and installation procedures can cause damage. ESD damage can range from subtle performance degradation to complete device failure. Precision integrated circuits may be more susceptible to damage because very small parametric changes could cause the device not to meet its published specifications. Logic Diagram Schematic (Each Darlington Pair) 2 Submit Documentation Feedback Copyright © 1997–2014, Texas Instruments Incorporated Product Folder Links :ULN2803A ULN2803A www.ti.com SLRS049F –FEBRUARY 1997–REVISED JANUARY 2014 Absolute Maximum Ratings(1) at 25°C free-air temperature (unless otherwise noted) VALUE UNIT Collector-emitter voltage 50 V Input voltage(2) 30 V Peak collector current 500 mA Output clamp current 500 mA Total substrate-terminal current –2.5 A D package 73.14 θJA Package thermal impedance(3) (4) °C/W DW package 62.66 TJ Operating virtual junction temperature 150 °C Tstg Storage temperature range –65 to 150 °C (1) Stresses beyond those listed under Absolute Maximum Ratings may cause permanent damage to the device. These are stress ratings only, and functional operation of the device at these or any other conditions beyond those indicated under Recommended Operating Conditions is not implied. Exposure to absolute-maximum-rated conditions for extended periods may affect device reliability. (2) All voltage values, unless otherwise noted, are with respect to the emitter/substrate terminal GND. (3) Maximum power dissipation is a function of TJ(max), θJA, and TA. The maximum allowable power dissipation at any allowable ambient temperature is PD = (TJ(max) – TA)/θJA. Operating at the absolute maximum TJ of 150°C can affect reliability. (4) The package thermal impedance is calculated in accordance with JESD 51-7. Electrical Characteristics at TA = 25°C free-air temperature (unless otherwise noted) ULN2002A PARAMETER TEST CONDITIONS UNIT MIN TYP MAX I VCE = 50 V, CEX Collector cutoff current see Figure 1 II = 0 50 μA I VCE = 50 V, IC = 500 μA, I(off) Off-state input current T 50 65 μA A = 70°C see Figure 2 II(on) Input current VI = 3.85 V, See Figure 3 0.93 1.35 mA IC = 200 mA 2.4 V VCE = 2 V, I(on) On-state input voltage see Figure 4 IC = 250 mA 2.7 V IC = 300 mA 3 II = 250 μA, IC = 100 mA 0.9 1.1 see Figure 5 V II = 350 μA, CE(sat) Collector-emitter saturation voltage see Figure 5 IC = 200 mA 1 1.3 V II = 500 μA, IC = 350 mA 1.3 1.6 see Figure 5 IR Clamp diode reverse current VR = 50 V, see Figure 6 50 μA VF Clamp diode forward voltage IF = 350 mA see Figure 7 1.7 2 V Ci Input capacitance VI = 0, f = 1 MHz 15 25 pF Switching Characteristics TA = 25°C PARAMETER TEST CONDITIONS MIN TYP MAX UNIT tPLH Propagation delay time, low- to high-level output VS = 50 V, CL = 15 pF, RL = 163 Ω, 130 See Figure 8 ns tPHL Propagation delay time, high- to low-level output 20 VOH High-level output voltage after switching VS = 50 V, IO = 300 mA, See Figure 9 VS – 20 mV Copyright © 1997–2014, Texas Instruments Incorporated Submit Documentation Feedback 3 Product Folder Links :ULN2803A Open VF IF Open VCE II IC hFE = IC II VR Open IR Open VCE IC VI Open II VI Open Open VCE IC II(off) Open VCE ICEX Open ULN2803A SLRS049F –FEBRUARY 1997–REVISED JANUARY 2014 www.ti.com Parameter Measurement Information Figure 1. ICEX Test Circuit Figure 2. II(off) Test Circuit Figure 3. II(on) Test Circuit Figure 4. VI(on) Test Circuit Figure 5. hFE, VCE(sat) Test Circuit Figure 6. IR Test Circuit Figure 7. VF Test Circuit 4 Submit Documentation Feedback Copyright © 1997–2014, Texas Instruments Incorporated Product Folder Links :ULN2803A Pulse Generator (see Note A) Input Open VS = 50 V RL = 163 ! CL = 15 pF (see Note B) Output tPHL tPLH 0.5 μs <5 ns <10 ns 90% 50% 10% 10% 90% 50% 50% 50% VIH Input (see Note C) Output 0 Test Circuit Voltage Waveforms VOH ULN2803A www.ti.com SLRS049F –FEBRUARY 1997–REVISED JANUARY 2014 Parameter Measurement Information (continued) A. The pulse generator has the following characteristics: PRR = 12.5 kHz, ZO = 50 Ω. B. CL includes probe and jig capacitance. C. VIH = 3 V Figure 8. Propagation Delay-Times Copyright © 1997–2014, Texas Instruments Incorporated Submit Documentation Feedback 5 Product Folder Links :ULN2803A Pulse Generator (see Note A) Input VS 163 ! CL = 15 pF (see Note B) Output 40 μs <5 ns <10 ns 90% 1.5 V 10% 10% 90% 1.5 V VIH Input (see Note C) Output 0 2 mH VOH Test Circuit Voltage Waveforms ULN2803A SLRS049F –FEBRUARY 1997–REVISED JANUARY 2014 www.ti.com Parameter Measurement Information (continued) A. The pulse generator has the following characteristics: PRR = 12.5 kHz, ZO = 50 Ω. B. CL includes probe and jig capacitance. C. VIH = 3 V Figure 9. Latch-Up Test 6 Submit Documentation Feedback Copyright © 1997–2014, Texas Instruments Incorporated Product Folder Links :ULN2803A ULN2803A www.ti.com SLRS049F –FEBRUARY 1997–REVISED JANUARY 2014 REVISION HISTORY Changes from Revision E (July 2006) to Revision F Page • Updated document to new TI data sheet format - no specification changes. ...................................................................... 1 • Deleted Ordering Information table. ...................................................................................................................................... 1 • Added ESD warning. ............................................................................................................................................................ 2 Copyright © 1997–2014, Texas Instruments Incorporated Submit Documentation Feedback 7 Product Folder Links :ULN2803A PACKAGE OPTION ADDENDUM www.ti.com 27-Jan-2014 Addendum-Page 1 PACKAGING INFORMATION Orderable Device Status (1) Package Type Package Drawing Pins Package Qty Eco Plan (2) Lead/Ball Finish (6) MSL Peak Temp (3) Op Temp (°C) Device Marking (4/5) Samples ULN2803ADW ACTIVE SOIC DW 18 40 Green (RoHS & no Sb/Br) CU NIPDAU Level-2-260C-1 YEAR -40 to 85 ULN2803A ULN2803ADWG4 ACTIVE SOIC DW 18 40 Green (RoHS & no Sb/Br) CU NIPDAU Level-2-260C-1 YEAR -40 to 85 ULN2803A ULN2803ADWR ACTIVE SOIC DW 18 2000 Green (RoHS & no Sb/Br) CU NIPDAU Level-2-260C-1 YEAR -40 to 85 ULN2803A ULN2803ADWRG4 ACTIVE SOIC DW 18 2000 Green (RoHS & no Sb/Br) CU NIPDAU Level-2-260C-1 YEAR -40 to 85 ULN2803A ULN2803AN ACTIVE PDIP N 18 20 Pb-Free (RoHS) CU NIPDAU N / A for Pkg Type -40 to 85 ULN2803AN ULN2803ANE4 ACTIVE PDIP N 18 20 Pb-Free (RoHS) CU NIPDAU N / A for Pkg Type -40 to 85 ULN2803AN (1) The marketing status values are defined as follows: ACTIVE: Product device recommended for new designs. LIFEBUY: TI has announced that the device will be discontinued, and a lifetime-buy period is in effect. NRND: Not recommended for new designs. Device is in production to support existing customers, but TI does not recommend using this part in a new design. PREVIEW: Device has been announced but is not in production. Samples may or may not be available. OBSOLETE: TI has discontinued the production of the device. (2) Eco Plan - The planned eco-friendly classification: Pb-Free (RoHS), Pb-Free (RoHS Exempt), or Green (RoHS & no Sb/Br) - please check http://www.ti.com/productcontent for the latest availability information and additional product content details. TBD: The Pb-Free/Green conversion plan has not been defined. Pb-Free (RoHS): TI's terms "Lead-Free" or "Pb-Free" mean semiconductor products that are compatible with the current RoHS requirements for all 6 substances, including the requirement that lead not exceed 0.1% by weight in homogeneous materials. Where designed to be soldered at high temperatures, TI Pb-Free products are suitable for use in specified lead-free processes. Pb-Free (RoHS Exempt): This component has a RoHS exemption for either 1) lead-based flip-chip solder bumps used between the die and package, or 2) lead-based die adhesive used between the die and leadframe. The component is otherwise considered Pb-Free (RoHS compatible) as defined above. Green (RoHS & no Sb/Br): TI defines "Green" to mean Pb-Free (RoHS compatible), and free of Bromine (Br) and Antimony (Sb) based flame retardants (Br or Sb do not exceed 0.1% by weight in homogeneous material) (3) MSL, Peak Temp. - The Moisture Sensitivity Level rating according to the JEDEC industry standard classifications, and peak solder temperature. (4) There may be additional marking, which relates to the logo, the lot trace code information, or the environmental category on the device. (5) Multiple Device Markings will be inside parentheses. Only one Device Marking contained in parentheses and separated by a "~" will appear on a device. If a line is indented then it is a continuation of the previous line and the two combined represent the entire Device Marking for that device. PACKAGE OPTION ADDENDUM www.ti.com 27-Jan-2014 Addendum-Page 2 (6) Lead/Ball Finish - Orderable Devices may have multiple material finish options. Finish options are separated by a vertical ruled line. Lead/Ball Finish values may wrap to two lines if the finish value exceeds the maximum column width. Important Information and Disclaimer:The information provided on this page represents TI's knowledge and belief as of the date that it is provided. TI bases its knowledge and belief on information provided by third parties, and makes no representation or warranty as to the accuracy of such information. Efforts are underway to better integrate information from third parties. TI has taken and continues to take reasonable steps to provide representative and accurate information but may not have conducted destructive testing or chemical analysis on incoming materials and chemicals. TI and TI suppliers consider certain information to be proprietary, and thus CAS numbers and other limited information may not be available for release. In no event shall TI's liability arising out of such information exceed the total purchase price of the TI part(s) at issue in this document sold by TI to Customer on an annual basis. TAPE AND REEL INFORMATION *All dimensions are nominal Device Package Type Package Drawing Pins SPQ Reel Diameter (mm) Reel Width W1 (mm) A0 (mm) B0 (mm) K0 (mm) P1 (mm) W (mm) Pin1 Quadrant ULN2803ADWR SOIC DW 18 2000 330.0 24.4 10.9 12.0 2.7 12.0 24.0 Q1 PACKAGE MATERIALS INFORMATION www.ti.com 27-Jan-2014 Pack Materials-Page 1 *All dimensions are nominal Device Package Type Package Drawing Pins SPQ Length (mm) Width (mm) Height (mm) ULN2803ADWR SOIC DW 18 2000 370.0 355.0 55.0 PACKAGE MATERIALS INFORMATION www.ti.com 27-Jan-2014 Pack Materials-Page 2 IMPORTANT NOTICE Texas Instruments Incorporated and its subsidiaries (TI) reserve the right to make corrections, enhancements, improvements and other changes to its semiconductor products and services per JESD46, latest issue, and to discontinue any product or service per JESD48, latest issue. Buyers should obtain the latest relevant information before placing orders and should verify that such information is current and complete. All semiconductor products (also referred to herein as “components”) are sold subject to TI’s terms and conditions of sale supplied at the time of order acknowledgment. TI warrants performance of its components to the specifications applicable at the time of sale, in accordance with the warranty in TI’s terms and conditions of sale of semiconductor products. Testing and other quality control techniques are used to the extent TI deems necessary to support this warranty. Except where mandated by applicable law, testing of all parameters of each component is not necessarily performed. TI assumes no liability for applications assistance or the design of Buyers’ products. Buyers are responsible for their products and applications using TI components. To minimize the risks associated with Buyers’ products and applications, Buyers should provide adequate design and operating safeguards. TI does not warrant or represent that any license, either express or implied, is granted under any patent right, copyright, mask work right, or other intellectual property right relating to any combination, machine, or process in which TI components or services are used. Information published by TI regarding third-party products or services does not constitute a license to use such products or services or a warranty or endorsement thereof. Use of such information may require a license from a third party under the patents or other intellectual property of the third party, or a license from TI under the patents or other intellectual property of TI. Reproduction of significant portions of TI information in TI data books or data sheets is permissible only if reproduction is without alteration and is accompanied by all associated warranties, conditions, limitations, and notices. TI is not responsible or liable for such altered documentation. Information of third parties may be subject to additional restrictions. Resale of TI components or services with statements different from or beyond the parameters stated by TI for that component or service voids all express and any implied warranties for the associated TI component or service and is an unfair and deceptive business practice. TI is not responsible or liable for any such statements. Buyer acknowledges and agrees that it is solely responsible for compliance with all legal, regulatory and safety-related requirements concerning its products, and any use of TI components in its applications, notwithstanding any applications-related information or support that may be provided by TI. Buyer represents and agrees that it has all the necessary expertise to create and implement safeguards which anticipate dangerous consequences of failures, monitor failures and their consequences, lessen the likelihood of failures that might cause harm and take appropriate remedial actions. Buyer will fully indemnify TI and its representatives against any damages arising out of the use of any TI components in safety-critical applications. In some cases, TI components may be promoted specifically to facilitate safety-related applications. With such components, TI’s goal is to help enable customers to design and create their own end-product solutions that meet applicable functional safety standards and requirements. Nonetheless, such components are subject to these terms. No TI components are authorized for use in FDA Class III (or similar life-critical medical equipment) unless authorized officers of the parties have executed a special agreement specifically governing such use. Only those TI components which TI has specifically designated as military grade or “enhanced plastic” are designed and intended for use in military/aerospace applications or environments. Buyer acknowledges and agrees that any military or aerospace use of TI components which have not been so designated is solely at the Buyer's risk, and that Buyer is solely responsible for compliance with all legal and regulatory requirements in connection with such use. TI has specifically designated certain components as meeting ISO/TS16949 requirements, mainly for automotive use. In any case of use of non-designated products, TI will not be responsible for any failure to meet ISO/TS16949. Products Applications Audio www.ti.com/audio Automotive and Transportation www.ti.com/automotive Amplifiers amplifier.ti.com Communications and Telecom www.ti.com/communications Data Converters dataconverter.ti.com Computers and Peripherals www.ti.com/computers DLP® Products www.dlp.com Consumer Electronics www.ti.com/consumer-apps DSP dsp.ti.com Energy and Lighting www.ti.com/energy Clocks and Timers www.ti.com/clocks Industrial www.ti.com/industrial Interface interface.ti.com Medical www.ti.com/medical Logic logic.ti.com Security www.ti.com/security Power Mgmt power.ti.com Space, Avionics and Defense www.ti.com/space-avionics-defense Microcontrollers microcontroller.ti.com Video and Imaging www.ti.com/video RFID www.ti-rfid.com OMAP Applications Processors www.ti.com/omap TI E2E Community e2e.ti.com Wireless Connectivity www.ti.com/wirelessconnectivity Mailing Address: Texas Instruments, Post Office Box 655303, Dallas, Texas 75265 Copyright © 2014, Texas Instruments Incorporated MAX3222/MAX3232/MAX3237/MAX3241* 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors ________________________________________________________________ Maxim Integrated Products 1 19-0273; Rev 7; 1/07 For pricing, delivery, and ordering information, please contact Maxim/Dallas Direct! at 1-888-629-4642, or visit Maxim’s website at www.maxim-ic.com. ________________General Description The MAX3222/MAX3232/MAX3237/MAX3241 transceivers have a proprietary low-dropout transmitter output stage enabling true RS-232 performance from a 3.0V to 5.5V supply with a dual charge pump. The devices require only four small 0.1μF external chargepump capacitors. The MAX3222, MAX3232, and MAX3241 are guaranteed to run at data rates of 120kbps while maintaining RS-232 output levels. The MAX3237 is guaranteed to run at data rates of 250kbps in the normal operating mode and 1Mbps in the MegaBaud™ operating mode, while maintaining RS-232 output levels. The MAX3222/MAX3232 have 2 receivers and 2 drivers. The MAX3222 features a 1μA shutdown mode that reduces power consumption and extends battery life in portable systems. Its receivers remain active in shutdown mode, allowing external devices such as modems to be monitored using only 1μA supply current. The MAX3222 and MAX3232 are pin, package, and functionally compatible with the industry-standard MAX242 and MAX232, respectively. The MAX3241 is a complete serial port (3 drivers/ 5 receivers) designed for notebook and subnotebook computers. The MAX3237 (5 drivers/3 receivers) is ideal for fast modem applications. Both these devices feature a shutdown mode in which all receivers can remain active while using only 1μA supply current. Receivers R1 (MAX3237/MAX3241) and R2 (MAX3241) have extra outputs in addition to their standard outputs. These extra outputs are always active, allowing external devices such as a modem to be monitored without forward biasing the protection diodes in circuitry that may have VCC completely removed. The MAX3222, MAX3232, and MAX3241 are available in space-saving TSSOP and SSOP packages. ________________________Applications Notebook, Subnotebook, and Palmtop Computers High-Speed Modems Battery-Powered Equipment Hand-Held Equipment Peripherals Printers __Next Generation Device Features ♦ For Smaller Packaging: MAX3228E/MAX3229E: +2.5V to +5.5V RS-232 Transceivers in UCSP™ ♦ For Integrated ESD Protection: MAX3222E/MAX3232E/MAX3237E/MAX3241E*/ MAX3246E: ±15kV ESD-Protected, Down to 10nA, 3.0V to 5.5V, Up to 1Mbps, True RS-232 Transceivers ♦ For Low-Voltage or Data Cable Applications: MAX3380E/MAX3381E: +2.35V to +5.5V, 1μA, 2Tx/2Rx RS-232 Transceivers with ±15kV ESD-Protected I/O and Logic Pins 18 17 16 15 14 13 12 11 1 2 3 4 5 6 7 8 SHDN VCC GND C1- T1OUT V+ C1+ EN TOP VIEW R1IN R1OUT T1IN T2OUT T2IN VC2- C2+ R2IN 9 10 R2OUT DIP/SO MAX3222 + MAX3222 _________________Pin Configurations _______________Ordering Information MegaBaud and UCSP are trademarks of Maxim Integrated Products, Inc. *Covered by U.S. Patent numbers 4,636,930; 4,679,134; 4,777,577; 4,797,899; 4,809,152; 4,897,774; 4,999,761; and other patents pending. Typical Operating Circuits appear at end of data sheet. Pin Configurations continued at end of data sheet. Ordering Information continued at end of data sheet. +Denotes lead-free package. PART TEMP RANGE PIN-PACKAGE PKG CODE MAX3222CUP+ 0°C to +70°C 20 TSSOP U20+2 MAX3222CAP+ 0°C to +70°C 20 SSOP A20+1 MAX3222CWN+ 0°C to +70°C 18 SO W18+1 MAX3222CPN+ 0°C to +70°C 18 Plastic Dip P18+5 VCC = 5.0V MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 2 _______________________________________________________________________________________ ABSOLUTE MAXIMUM RATINGS ELECTRICAL CHARACTERISTICS (VCC = +3.0V to +5.5V, C1–C4 = 0.1μF (Note 2), TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) Stresses beyond those listed under “Absolute Maximum Ratings” may cause permanent damage to the device. These are stress ratings only, and functional operation of the device at these or any other conditions beyond those indicated in the operational sections of the specifications is not implied. Exposure to absolute maximum rating conditions for extended periods may affect device reliability. Note 1: V+ and V- can have a maximum magnitude of 7V, but their absolute difference cannot exceed 13V. VCC...........................................................................-0.3V to +6V V+ (Note 1) ...............................................................-0.3V to +7V V- (Note 1) ................................................................+0.3V to -7V V+ + V- (Note 1)...................................................................+13V Input Voltages T_IN, SHDN, EN ...................................................-0.3V to +6V MBAUD...................................................-0.3V to (VCC + 0.3V) R_IN .................................................................................±25V Output Voltages T_OUT...........................................................................±13.2V R_OUT....................................................-0.3V to (VCC + 0.3V) Short-Circuit Duration T_OUT ....................................................................Continuous Continuous Power Dissipation (TA = +70°C) 16-Pin TSSOP (derate 6.7mW/°C above +70°C).............533mW 16-Pin Narrow SO (derate 8.70mW/°C above +70°C) ....696mW 16-Pin Wide SO (derate 9.52mW/°C above +70°C)........762mW 16-Pin Plastic DIP (derate 10.53mW/°C above +70°C)...842mW 18-Pin SO (derate 9.52mW/°C above +70°C)..............762mW 18-Pin Plastic DIP (derate 11.11mW/°C above +70°C) ..889mW 20-Pin SSOP (derate 7.00mW/°C above +70°C) .........559mW 20-Pin TSSOP (derate 8.0mW/°C above +70°C).............640mW 28-Pin TSSOP (derate 8.7mW/°C above +70°C).............696mW 28-Pin SSOP (derate 9.52mW/°C above +70°C) .........762mW 28-Pin SO (derate 12.50mW/°C above +70°C) .....................1W Operating Temperature Ranges MAX32_ _C_ _.....................................................0°C to +70°C MAX32_ _E_ _ .................................................-40°C to +85°C Storage Temperature Range .............................-65°C to +150°C Lead Temperature (soldering, 10s) .................................+300°C IOUT = -1.0mA IOUT = 1.6mA Receivers disabled T_IN, EN, SHDN, MBAUD T_IN, EN, SHDN, MBAUD CONDITIONS V 0.8 1.5 Input Threshold Low 0.6 1.2 Input Voltage Range -25 25 V 0.5 2.0 VCC Power-Supply Current Output Voltage High VCC - 0.6 VCC - 0.1 V Output Voltage Low 0.4 V Output Leakage Current ±0.05 ±10 μA Input Leakage Current ±0.01 ±1.0 μA 0.8 V Input Logic Threshold Low (Note 3) PARAMETER MIN TYP MAX UNITS TA = +25°C TA = +25°C V 1.8 2.4 Input Threshold High 1.5 2.4 VCC = 3.3V VCC = 5.0V 2.0 V 2.4 Input Logic Threshold High (Note 3) No load, VCC = 3.3V or 5.0V, TA = +25°C mA 0.3 1.0 MAX3222/MAX3232/ MAX3241 MAX3237 Shutdown Supply Current SHDN = GND, TA = +25°C 1.0 10 μA VCC = 3.3V VCC = 5.0V VCC = 3.3V VCC = 5.0V DC CHARACTERISTICS LOGIC INPUTS AND RECEIVER OUTPUTS RECEIVER INPUTS MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors _______________________________________________________________________________________ 3 TIMING CHARACTERISTICS—MAX3222/MAX3232/MAX3241 (VCC = +3.0V to +5.5V, C1–C4 = 0.1μF (Note 2), TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) ELECTRICAL CHARACTERISTICS (continued) (VCC = +3.0V to +5.5V, C1–C4 = 0.1μF (Note 2), TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) T1IN = T2IN = GND, T3IN = VCC, T3OUT loaded with 3kΩ to GND, T1OUT and T2OUT loaded with 2.5mA each CONDITIONS Transmitter Output Voltage ±5.0 V Input Hysteresis 0.3 V PARAMETER MIN TYP MAX UNITS Output Voltage Swing All transmitter outputs loaded with 3kΩ to ground ±5.0 ±5.4 V Output Short-Circuit Current ±35 ±60 mA Output Resistance VCC = V+ = V- = 0V, VOUT = ±2V 300 10M Ω VOUT = ±12V, VCC = 0V or 3V to 5.5V, transmitters disabled Output Leakage Current ±25 μA tPHL RL = 3kΩ, CL = 1000pF, one transmitter switching tPLH | tPHL - tPLH | | tPHL - tPLH | Normal operation Normal operation CONDITIONS 0.3 μs 0.3 Receiver Propagation Delay Maximum Data Rate 120 235 kbps Receiver Skew 300 ns Transmitter Skew 300 ns Receiver Output Disable Time 200 ns Receiver Output Enable Time 200 ns PARAMETER MIN TYP MAX UNITS VCC = 3.3V, RL = 3kΩ to 7kΩ, 6 30 +3V to -3V or -3V to +3V, TA = +25°C, one transmitter switching V/μs 4 30 Transition-Region Slew Rate R_IN to R_OUT, CL = 150pF CL = 150pF to 1000pF CL = 150pF to 2500pF Input Resistance TA = +25°C 3 5 7 kΩ MOUSE DRIVEABILITY (MAX3241) TRANSMITTER OUTPUTS MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 4 _______________________________________________________________________________________ __________________________________________Typical Operating Characteristics (VCC = +3.3V, 235kbps data rate, 0.1μF capacitors, all transmitters loaded with 3kΩ, TA = +25°C, unless otherwise noted.) RL = 3kΩ, CL = 1000pF, one transmitter switching, MBAUD = GND Normal operation CONDITIONS Receiver Output Disable Time Normal operation 200 ns | tPHL - tPLH |, MBAUD = GND 100 ns 0.15 250 μs 0.15 Receiver Propagation Delay Receiver Output Enable Time 200 ns PARAMETER MIN TYP MAX UNITS TIMING CHARACTERISTICS—MAX3237 (VCC = +3.0V to +5.5V, C1–C4 = 0.1μF (Note 2), TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) VCC = 3.0V to 4.5V, RL = 3kΩ, CL = 250pF, one transmitter switching, MBAUD = VCC 1000 VCC = 4.5V to 5.5V, RL = 3kΩ, CL = 1000pF, one transmitter switching, MBAUD = VCC kbps 1000 Maximum Data Rate R_IN to R_OUT, CL = 150pF | tPHL - tPLH |, MBAUD = VCC 25 ns Transmitter Skew Receiver Skew | tPHL - tPLH | 50 ns 6 30 V/μs 4 30 tPHL tPLH CL = 150pF to 2500pF, MBAUD = GND CL = 150pF to 1000pF MBAUD = GND VCC = 3.3V, RL = 3Ω to 7kΩ, +3V to -3V or -3V to +3V, TA = +25°C Transition-Region Slew Rate MBAUD = VCC 24 150 Note 2: MAX3222/MAX3232/MAX3241: C1–C4 = 0.1μF tested at 3.3V ±10%; C1 = 0.047μF, C2–C4 = 0.33μF tested at 5.0V ±10%. MAX3237: C1–C4 = 0.1μF tested at 3.3V ±5%; C1–C4 = 0.22μF tested at 3.3V ±10%; C1 = 0.047μF, C2–C4 = 0.33μF tested at 5.0V ±10%. Note 3: Transmitter input hysteresis is typically 250mV. -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 0 MAX3222/MAX3232 TRANSMITTER OUTPUT VOLTAGE vs. LOAD CAPACITANCE MAX3222-01 LOAD CAPACITANCE (pF) TRANSMITTER OUTPUT VOLTAGE (V) 1000 2000 3000 4000 5000 VOUT+ VOUT- 0 2 4 6 8 10 12 14 16 18 20 22 150 MAX3222/MAX3232 SLEW RATE vs. LOAD CAPACITANCE MAX3222-02 LOAD CAPACITANCE (pF) SLEW RATE (V/μs) 1000 2000 3000 4000 5000 FOR DATA RATES UP TO 235kbps +SLEW -SLEW 0 5 10 15 20 25 30 35 40 0 MAX3222/MAX3232 SUPPLY CURRENT vs. LOAD CAPACITANCE WHEN TRANSMITTING DATA MAX3222-03 LOAD CAPACITANCE (pF) SUPPLY CURRENT (mA) 1000 2000 3000 4000 5000 235kbps 120kbps 20kbps MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors _______________________________________________________________________________________ 5 -7.5 -5.0 -2.5 0 2.5 5.0 7.5 0 MAX3241 TRANSMITTER OUTPUT VOLTAGE vs. LOAD CAPACITANCE MAX3222-04 LOAD CAPACITANCE (pF) TRANSMITTER OUTPUT VOLTAGE (V) 1000 2000 3000 4000 5000 VOUT+ 1 TRANSMITTER AT 235kbps 2 TRANSMITTERS AT 30kbps VOUTALL OUTPUTS LOADED WITH 3kΩ +CL 0.1μF CHARGE-PUMP CAPACITORS 4 FOR ALL DATA RATES UP TO 235kbps 6 8 10 12 14 16 18 20 22 24 0 MAX3241 SLEW RATE vs. LOAD CAPACITANCE MAX3222-05 LOAD CAPACITANCE (pF) SLEW RATE (V/μs) 1000 2000 3000 4000 5000 +SLEW -SLEW 0 5 10 15 20 25 30 35 45 40 0 MAX3241 SUPPLY CURRENT vs. LOAD CAPACITANCE WHEN TRANSMITTING DATA MAX3222-06 LOAD CAPACITANCE (pF) SUPPLY CURRENT (mA) 1000 2000 3000 4000 5000 235kbps 120kbps 20kbps -7.5 -5.0 -2.5 0 2.5 5.0 7.5 0 MAX3237 TRANSMITTER OUTPUT VOLTAGE vs. LOAD CAPACITANCE (MBAUD = GND) MAX3222-07 LOAD CAPACITANCE (pF) TRANSMITTER OUTPUT VOLTAGE (V) 1000 2000 3000 4000 5000 1 TRANSMITTER AT 240kbps 4 TRANSMITTERS AT 15kbps 3kΩ + CL LOADS VCC = 3.3V 0 10 20 30 50 40 60 70 0 MAX3237 SLEW RATE vs. LOAD CAPACITANCE (MBAUD = VCC) MAX3222-10 LOAD CAPACITANCE (pF) SLEW RATE (V/μs) 500 1000 1500 2000 -SLEW, 1Mbps +SLEW, 1Mbps 1 TRANSMITTER AT FULL DATA RATE 4 TRANSMITTERS AT 1/16 DATA RATE 3kΩ + CL LOAD EACH OUTPUT VCC = 3.3V -SLEW, 2Mbps +SLEW, 2Mbps -7.5 -5.0 -2.5 0 2.5 5.0 7.5 0 MAX3237 TRANSMITTER OUTPUT VOLTAGE vs. LOAD CAPACITANCE (MBAUD = VCC) MAX3222-08 LOAD CAPACITANCE (pF) TRANSMITTER OUTPUT VOLTAGE (V) 500 1000 1500 2000 1 TRANSMITTER AT FULL DATA RATE 4 TRANSMITTERS AT 1/16 DATA RATE 3kΩ + CL LOAD, EACH OUTPUT VCC = 3.3V 2Mbps 1.5Mbps 1Mbps 2Mbps 1Mbps 1.5Mbps 0 10 20 30 40 50 60 0 MAX3237 SUPPLY CURRENT vs. LOAD CAPACITANCE (MBAUD = GND) MAX3222-11 LOAD CAPACITANCE (pF) SUPPLY CURRENT (mA) 1000 2000 3000 4000 5000 240kbps 120kbps 20kbps 1 TRANSMITTER AT FULL DATA RATE 4 TRANSMITTERS AT 1/16 DATA RATE 3kΩ + CL LOADS VCC = 3.3V 0 2 4 6 8 10 12 0 MAX3237 SLEW RATE vs. LOAD CAPACITANCE (MBAUD = GND) MAX3222-09 LOAD CAPACITANCE (pF) SLEW RATE (V/μs) 1000 2000 3000 4000 5000 +SLEW -SLEW 1 TRANSMITTER AT 240kbps 4 TRANSMITTERS AT 15kbps 3kΩ + CL LOADS VCC = 3.3V 0 10 30 20 40 50 60 70 0 MAX3237 SKEW vs. LOAD CAPACITANCE (tPLH - tPHL) MAX3222-12 LOAD CAPACITANCE (pF) 500 1000 1500 2000 2500 MAX MIN AVERAGE; 10 PARTS SKEW (ns) 1 TRANSMITTER AT 512kbps 4 TRANSMITTERS AT 32kbps 3kΩ + CL LOADS VCC = 3.3V MBAUD = VCC _____________________________Typical Operating Characteristics (continued) (VCC = +3.3V, 235kbps data rate, 0.1μF capacitors, all transmitters loaded with 3kΩ, TA = +25°C, unless otherwise noted.) MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 6 _______________________________________________________________________________________ — FUNCTION — MAX3222 Noninverting Complementary Receiver Outputs. Always active. DIP/SO SSOP — 11, 14 1 1 Receiver Enable. Active low. 2 2 Positive Terminal of Voltage-Doubler Charge-Pump Capacitor 6 6 Negative Terminal of Inverting Charge-Pump Capacitor 5 5 Positive Terminal of Inverting Charge-Pump Capacitor 4 4 Negative Terminal of Voltage-Doubler Charge-Pump Capacitor 3 3 +5.5V Generated by the Charge Pump 11, 12 12, 13 TTL/CMOS Transmitter Inputs 10, 13 10, 15 TTL/CMOS Receiver Outputs 9, 14 9, 16 RS-232 Receiver Inputs 8, 15 8, 17 RS-232 Transmitter Outputs 7 7 -5.5V Generated by the Charge Pump 18 20 Shutdown Control. Active low. 17 19 +3.0V to +5.5V Supply Voltage 16 18 Ground No Connection MAX3232 MAX3237 — 16 — 13 1 28 5 3 4 1 3 25 2 27 10, 11 17, 19, 22, 23, 24 9, 12 18, 20, 21 8, 13 8, 9, 11 7, 14 5, 6, 7, 10, 12 6 4 — 14 16 26 15 2 — — NAME EN C1+ C2- C2+ C1- V+ T_IN R_OUT R_IN T_OUT VSHDN VCC GND R_OUTB N.C. MAX3241 20, 21 23 28 2 1 24 27 12, 13, 14 15–19 4–8 9, 10, 11 3 22 26 25 — PIN — — MegaBaud Control Input. Connect to GND for normal operation; connect to VCC for 1Mbps transmission rates. — 15 — MBAUD ______________________________________________________________Pin Description MAX3222/MAX3232/MAX3237/MAX3241 _______________Detailed Description Dual Charge-Pump Voltage Converter The MAX3222/MAX3232/MAX3237/MAX3241’s internal power supply consists of a regulated dual charge pump that provides output voltages of +5.5V (doubling charge pump) and -5.5V (inverting charge pump), regardless of the input voltage (VCC) over the 3.0V to 5.5V range. The charge pumps operate in a discontinuous mode; if the output voltages are less than 5.5V, the charge pumps are enabled, and if the output voltages exceed 5.5V, the charge pumps are disabled. Each charge pump requires a flying capacitor (C1, C2) and a reservoir capacitor (C3, C4) to generate the V+ and V- supplies. RS-232 Transmitters The transmitters are inverting level translators that convert CMOS-logic levels to 5.0V EIA/TIA-232 levels. The MAX3222/MAX3232/MAX3241 transmitters guarantee a 120kbps data rate with worst-case loads of 3kΩ in parallel with 1000pF, providing compatibility with PC-to- PC communication software (such as LapLink™). Typically, these three devices can operate at data rates of 235kbps. Transmitters can be paralleled to drive multiple receivers or mice. The MAX3222/MAX3237/MAX3241’s output stage is turned off (high impedance) when the device is in shutdown mode. When the power is off, the MAX3222/ MAX3232/MAX3237/MAX3241 permit the outputs to be driven up to ±12V. The transmitter inputs do not have pullup resistors. Connect unused inputs to GND or VCC. MAX3237 MegaBaud Operation In normal operating mode (MBAUD = GND), the MAX3237 transmitters guarantee a 250kbps data rate with worst-case loads of 3kΩ in parallel with 1000pF. This provides compatibility with PC-to-PC communication software, such as LapLink. For higher speed serial communications, the MAX3237 features MegaBaud operation. In MegaBaud operating mode (MBAUD = VCC), the MAX3237 transmitters guarantee a 1Mbps data rate with worst-case loads of 3kΩ in parallel with 250pF for 3.0V < VCC < 4.5V. For 5V ±10% operation, the MAX3237 transmitters guarantee a 1Mbps data rate into worst-case loads of 3kΩ in parallel with 1000pF. MAX3222 MAX3232 MAX3237 MAX3241 5kΩ R_ OUT R_ IN EN* C2- C2+ C1- C1+ VV+ VCC C4 C1 C3 C2 0.1μF VCC SHDN* T_ IN T_ OUT GND VCC 0V 7kΩ 150pF MAX3222 MAX3232 MAX3237 MAX3241 5kΩ R_ OUT R_ IN EN* C2- C2+ C1- C1+ VV+ VCC C4 C1 C3 C2 0.1μF VCC SHDN* T_ IN T_ OUT GND VCC 0V 3kΩ 2500pF MINIMUM SLEW-RATE TEST CIRCUIT MAXIMUM SLEW-RATE TEST CIRCUIT *MAX3222/MAX3237/MAX3241 ONLY Figure 1. Slew-Rate Test Circuits LapLink is a trademark of Traveling Software, Inc. 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors _______________________________________________________________________________________ 7 MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 8 _______________________________________________________________________________________ RS-232 Receivers The receivers convert RS-232 signals to CMOS-logic output levels. The MAX3222/MAX3237/MAX3241 receivers have inverting three-state outputs. In shutdown, the receivers can be active or inactive (Table 1). The complementary outputs on the MAX3237 (R1OUTB) and the MAX3241 (R1OUTB, R2OUTB) are always active, regardless of the state of EN or SHDN. This allows for Ring Indicator applications without forward biasing other devices connected to the receiver outputs. This is ideal for systems where VCC is set to 0V in shutdown to accommodate peripherals, such as UARTs (Figure 2). MAX3222/MAX3237/MAX3241 Shutdown Mode Supply current falls to less than 1μA in shutdown mode (SHDN = low). When shut down, the device’s charge pumps are turned off, V+ is pulled down to VCC, V- is pulled to ground, and the transmitter outputs are disabled (high impedance). The time required to exit shutdown is typically 100μs, as shown in Figure 3. Connect SHDN to VCC if the shutdown mode is not used. SHDN has no effect on R_OUT or R_OUTB. MAX3222/MAX3237/MAX3241 Enable Control The inverting receiver outputs (R_OUT) are put into a high-impedance state when EN is high. The complementary outputs R1OUTB and R2OUTB are always active, regardless of the state of EN and SHDN (Table 1). EN has no effect on T_OUT. __________Applications Information Capacitor Selection The capacitor type used for C1–C4 is not critical for proper operation; polarized or nonpolarized capacitors can be used. The charge pump requires 0.1μF capacitors for 3.3V operation. For other supply voltages, refer to Table 2 for required capacitor values. Do not use values lower than those listed in Table 2. Increasing the capacitor values (e.g., by a factor of 2) reduces ripple on the transmitter outputs and slightly reduces power consumption. C2, C3, and C4 can be increased without changing C1’s value. However, do not increase C1 without also increasing the values of C2, C3, and C4, to maintain the proper ratios (C1 to the other capacitors). When using the minimum required capacitor values, make sure the capacitor value does not degrade excessively with temperature. If in doubt, use capacitors with a higher nominal value. The capacitor’s equivalent series resistance (ESR), which usually rises at low temperatures, influences the amount of ripple on V+ and V-. MAX3237 MAX3241 T1OUT R1OUTB Tx UART VCC T1IN LOGIC TRANSITION DETECTOR R1OUT R1IN EN = VCC SHDN = GND VCC TO μP Rx PREVIOUS RS-232 Tx UART PROTECTION DIODE PROTECTION DIODE SHDN = GND VCC VCC GND Rx 5kΩ a) OLDER RS-232: POWERED-DOWN UART DRAWS CURRENT FROM ACTIVE RECEIVER OUTPUT IN SHUTDOWN. b) NEW MAX3237/MAX3241: EN SHUTS DOWN RECEIVER OUTPUTS (EXCEPT FOR B OUTPUTS), SO NO CURRENT FLOWS TO UART IN SHUTDOWN. B OUTPUTS INDICATE RECEIVER ACTIVITY DURING SHUTDOWN WITH EN HIGH. GND 5kΩ Figure 2. Detection of RS-232 Activity when the UART and Interface are Shut Down; Comparison of MAX3237/MAX3241 (b) with Previous Transceivers (a). MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors _______________________________________________________________________________________ 9 Power-Supply Decoupling In most circumstances, a 0.1μF bypass capacitor is adequate. In applications that are sensitive to powersupply noise, decouple VCC to ground with a capacitor of the same value as charge-pump capacitor C1. Connect bypass capacitors as close to the IC as possible. Operation Down to 2.7V Transmitter outputs will meet EIA/TIA-562 levels of ±3.7V with supply voltages as low as 2.7V. Transmitter Outputs when Exiting Shutdown Figure 3 shows two transmitter outputs when exiting shutdown mode. As they become active, the two transmitter outputs are shown going to opposite RS-232 levels (one transmitter input is high, the other is low). Each transmitter is loaded with 3kΩ in parallel with 2500pF. The transmitter outputs display no ringing or undesirable transients as they come out of shutdown. Note that the transmitters are enabled only when the magnitude of V- exceeds approximately 3V. Mouse Driveability The MAX3241 has been specifically designed to power serial mice while operating from low-voltage power supplies. It has been tested with leading mouse brands from manufacturers such as Microsoft and Logitech. The MAX3241 successfully drove all serial mice tested and met their respective current and voltage requirements. Figure 4a shows the transmitter output voltages under increasing load current at 3.0V. Figure 4b shows a typical mouse connection using the MAX3241. Table 1. MAX3222/MAX3237/MAX3241 Shutdown and Enable Control Truth Table Table 2. Required Minimum Capacitor Values 5V/div VCC = 3.3V C1–C4 = 0.1μF 2V/div T2 50μs/div T1 Figure 3. Transmitter Outputs when Exiting Shutdown or Powering Up VCC (V) C1 (μF) 4.5 to 5.5 0.047 3.0 to 5.5 0.1 C2, C3, C4 (μF) MAX3222/MAX3232/MAX3241 0.33 0.47 1 Active 0 1 1 Active SHDN 0 1 High-Z 0 0 High-Z EN T_OUT High-Z Active High-Z Active R_OUT R_OUTB (MAX3237/ MAX3241) Active Active Active Active 3.0 to 3.6 0.22 3.15 to 3.6 0.1 MAX3237 0.22 0.1 4.5 to 5.5 0.047 3.0 to 5.5 0.22 0.33 1.0 3.0 to 3.6 0.1 0.1 MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 10 ______________________________________________________________________________________ MAX3241 23 EN 15 R5OUT 16 R4OUT 17 R3OUT 18 R2OUT 19 R1OUT 20 R2OUTB 21 R1OUTB 5kΩ 5kΩ 5kΩ 5kΩ 5kΩ R5IN 8 R4IN 7 6 R2IN 5 R1IN 4 SHDN 22 GND 25 12 T3IN 13 T2IN 14 T1IN 2 C2- 1 C2+ 24 C1- 28 C1+ T3OUT 11 +V COMPUTER SERIAL PORT MOUSE +V -V GND Tx T2OUT 10 T1OUT 9 V- 3 V+ VCC 27 VCC C4 C1 C3 C2 0.1μF VCC = 3V to 5.5V 26 R3IN Figure 4b. Mouse Driver Test Circuit -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 0 1 2 3 4 5 6 7 MAX3222-04 LOAD CURRENT PER TRANSMITTER (mA) TRANSMITTER OUTPUT VOLTAGE (V) VOUT+ VCC = 3.0V VOUTVOUT+ VCC VOUTT1 T2 T3 Figure 4a. MAX3241 Transmitter Output Voltage vs. Load Current per Transmitter MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors ______________________________________________________________________________________ 11 High Data Rates The MAX3222/MAX3232/MAX3241 maintain the RS-232 ±5.0V minimum transmitter output voltage even at high data rates. Figure 5 shows a transmitter loopback test circuit. Figure 6 shows a loopback test result at 120kbps, and Figure 7 shows the same test at 235kbps. For Figure 6, all transmitters were driven simultaneously at 120kbps into RS-232 loads in parallel with 1000pF. For Figure 7, a single transmitter was driven at 235kbps, and all transmitters were loaded with an RS-232 receiver in parallel with 1000pF. The MAX3237 maintains the RS-232 ±5.0V minimum transmitter output voltage at data rates up to 1Mbps. Figure 8 shows a loopback test result at 1Mbps with MBAUD = VCC. For Figure 8, all transmitters were loaded with an RS-232 receiver in parallel with 250pF. MAX3222 MAX3232 MAX3237 MAX3241 5kΩ R_ OUT R_ IN EN* C2- C2+ C1- C1+ VV+ VCC C4 C1 C3 C2 0.1μF VCC SHDN* T_ IN T_ OUT GND VCC 0V 1000pF *MAX3222/MAX3237/MAX3241 ONLY T1IN R1OUT VCC = 3.3V T1OUT 5V/div 5V/div 5V/div 5μs/div Figure 5. Loopback Test Circuit Figure 6. MAX3241 Loopback Test Result at 120kbps T1IN R1OUT VCC = 3.3V T1OUT 5V/div 5V/div 2μs/div 5V/div Figure 7. MAX3241 Loopback Test Result at 235kbps +5V 0V +5V 0V -5V +5V 0V T_IN T_OUT = R_IN 5kΩ + 250pF R_OUT 150pF 200ns/div VCC = 3.3V Figure 8. MAX3237 Loopback Test Result at 1000kbps (MBAUD = VCC) MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 12 ______________________________________________________________________________________ __________________________________________________Typical Operating Circuits MAX3222 10 R2OUT 1 13 R1OUT R2IN 9 18 GND 16 RS-232 OUTPUTS TTL/CMOS INPUTS 11 T2IN 12 T1IN C2- 6 5 C2+ 4 C1- 2 C1+ R1IN 14 T2OUT 8 T1OUT 15 V- 7 V+ VCC 3 17 C1 0.1μF C2 0.1μF 0.1μF +3.3V RS-232 INPUTS TTL/CMOS OUTPUTS EN SHDN C3* 0.1μF C4 0.1μF PIN NUMBERS REFER TO DIP/SO PACKAGES. * C3 CAN BE RETURNED TO EITHER VCC OR GROUND. MAX3232 9 R2OUT 12 R1OUT R2IN 8 GND 15 RS-232 OUTPUTS TTL/CMOS INPUTS 10 T2IN 11 T1IN C2- 5 4 C2+ 3 C1- 1 C1+ R1IN 13 T2OUT 7 T1OUT 14 V- 6 V+ VCC 2 C4 0.1μF 16 0.1μF 0.1μF 0.1μF +3.3V RS-232 INPUTS TTL/CMOS OUTPUTS C3* 0.1μF * C3 CAN BE RETURNED TO EITHER VCC OR GROUND. SEE TABLE 2 FOR CAPACITOR SELECTION 5kΩ 5kΩ 5kΩ 5kΩ Interconnection with 3V and 5V Logic The MAX3222/MAX3232/MAX3237/MAX3241 can directly interface with various 5V logic families, including ACT and HCT CMOS. See Table 3 for more information on possible combinations of interconnections. Table 3. Logic-Family Compatibility with Various Supply Voltages Compatible with ACT and HCT CMOS, and with TTL. Incompatible with AC, HC, and CD4000 CMOS. 5 3.3 SYSTEM POWERSUPPLY VOLTAGE (V) Compatible with all TTL and CMOS-logic families. 5 5 Compatible with all CMOS families. 3.3 3.3 COMPATIBILITY MAX32_ _ VCC SUPPLY VOLTAGE (V) MAX3222/MAX3232/MAX3237/MAX3241 _____________________________________Typical Operating Circuits (continued) MAX3241 23 EN 15 R5OUT 16 R4OUT 17 R3OUT 18 R2OUT 19 R1OUT 20 R2OUTB 21 R1OUTB TTL/CMOS OUTPUTS 5kΩ 5kΩ 5kΩ 5kΩ 5kΩ R5IN 8 * C3 CAN BE RETURNED TO EITHER VCC OR GROUND. R4IN 7 R3IN 6 R2IN 5 R1IN 4 RS-232 INPUTS SHDN 22 GND 25 RS-232 OUTPUTS TTL/CMOS INPUTS 12 T3IN 13 T2IN 14 T1IN C2- 2 1 C2+ 24 C1- 28 C1+ T3OUT 11 T2OUT 10 T1OUT 9 V- 3 V+ VCC 27 C4 0.1μF C3* 0.1μF 0.1μF 0.1μF 0.1μF +3.3V 26 MAX3237 13 EN 18 R3OUT 20 R2OUT 21 R1OUT 16 R1OUTB LOGIC OUTPUTS 5kΩ 5kΩ 5kΩ * C3 CAN BE RETURNED TO EITHER VCC OR GROUND. R3IN 11 R2IN 9 R1IN 8 RS-232 INPUTS GND 2 RS-232 OUTPUTS LOGIC INPUTS 22 T3IN 23 T2IN 24 T1IN C2- 3 1 C2+ 25 C1- 28 C1+ T3OUT 7 T2OUT 6 T1OUT 5 T1 T2 T3 R1 R2 R3 V- 4 V+ VCC 27 0.1μF 0.1μF 0.1μF 0.1μF 0.1μF 26 MBAUD 15 17 T5IN 19 T4IN T5OUT 12 T4OUT 10 SHDN 14 T4 T5 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors ______________________________________________________________________________________ 13 MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 14 ______________________________________________________________________________________ _____________________________________________Pin Configurations (continued) 16 15 14 13 12 11 10 9 1 2 3 4 5 6 7 8 VCC GND T1OUT C2+ R1IN C1- V+ C1+ MAX3232 R1OUT T1IN T2IN R2IN R2OUT T2OUT VC2- DIP/SO/SSOP/TSSOP + TOP VIEW 20 19 18 17 16 15 14 13 1 2 3 8 12 10 11 4 5 6 7 SHDN VCC GND C1- T1OUT V+ C1+ EN R1IN R1OUT T1IN T2IN T2OUT VC2- C2+ R2IN 9 R2OUT SSOP/TSSOP + N.C. N.C. MAX3222 28 27 26 25 24 23 22 21 20 19 18 17 16 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 C1+ V+ VCC C1- T1IN T2IN MBAUD T3IN R1OUT R2OUT T4IN R3OUT T5IN R1OUTB SHDN EN T5OUT R3IN T4OUT R2IN R1IN T3OUT T2OUT T1OUT VC2- GND C2+ SSOP MAX3237 + 28 27 26 25 24 23 22 21 20 19 18 17 16 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 C1+ V+ VCC GND C1- EN R5OUT SHDN R1OUTB R2OUTB R1OUT R2OUT R3OUT R4OUT T1IN T2IN T3IN T3OUT T2OUT T1OUT R5IN R4IN R3IN R2IN R1IN VC2- C2+ SO/SSOP/TSSOP MAX3241 + MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors ______________________________________________________________________________________ 15 ______3V-Powered EIA/TIA-232 and EIA/TIA-562 Transceivers from Maxim Ordering Information (continued) *Dice are tested at TA = +25°C, DC parameters only. +Denotes lead-free package. 0.1μF capacitors, 1 complementary receiver, MegaBaud operation MAX3237 3.0 to 5.5 5/3 3 250/1000 232 0.1μF capacitors, AutoShutdown, complementary receiver, drives mice MAX3243 3.0 to 5.5 3/5 1 120 232 MAX3232 3.0 to 5.5 2/2 N/A 120 232 0.1μF capacitors MAX3223 3.0 to 5.5 2/2 2 120 232 0.1μF capacitors, AutoShutdown MAX3222 3.0 to 5.5 2/2 2 120 232 0.1μF capacitors 232 562 232 562 232 EIA/ TIA-232 OR 562 2.7 to 3.6 AutoShutdown, complementary receiver, drives mice, transient detection MAX3212 3/5 5 235 MAX563 3.0 to 3.6 2/2 2 230 0.1μF capacitors Operates directly from batteries without a voltage regulator MAX218 1.8 to 4.25 2/2 2 120 No. OF RECEIVERS ACTIVE IN SHUTDOWN POWERSUPPLY VOLTAGE (V) MAX562 2.7 to 5.25 3/5 5 230 Wide supply range MAX212 3.0 to 3.6 3/5 5 120 Drives mice FEATURES GUARANTEED DATA RATE (kbps) No. OF TRANSMITTERS/ RECEIVERS PART 0.1μF capacitors, 2 complementary receivers, drives mice MAX3241 3.0 to 5.5 3/5 5 120 232 PART TEMP RANGE PIN-PACKAGE PKG CODE MAX3222EUP+ -40°C to +85°C 20 TSSOP U20+2 MAX3222EAP+ -40°C to +85°C 20 SSOP A20+1 MAX3222EWN+ -40°C to +85°C 18 SO W18+1 MAX3222EPN+ -40°C to +85°C 18 Plastic Dip P18+5 MAX3222C/D 0°C to +70°C Dice* — MAX3232CUE+ 0°C to +70°C 16 TSSOP U16+1 MAX3232CSE+ 0°C to +70°C 16 Narrow SO S16+1 MAX3232CWE+ 0°C to +70°C 16 Wide SO W16+1 MAX3232CPE+ 0°C to +70°C 16 Plastic DIP P16+1 MAX3232EUE+ -40°C to +85°C 16 TSSOP U16+1 MAX3232ESE+ -40°C to +85°C 16 Narrow SO S16+5 PART TEMP RANGE PIN-PACKAGE PKG CODE MAX3232EWE+ -40°C to +85°C 16 Wide SO W16+1 MAX3232EPE+ -40°C to +85°C 16 Plastic DIP P16+1 MAX3232CAE+ 0°C to +70°C 16 SSOP A28+2 MAX3237CAI+ 0°C to +70°C 28 SSOP A28+1 MAX3237ENI+ -40°C to +85°C 28 SSOP A28+1 MAX3241CAI+ 0°C to +70°C 28 SSOP A28+1 MAX3241CWI+ 0°C to +70°C 28 SO W28+6 MAX3241EUI+ -40°C to +85°C 28 TSSOP U28+2 MAX3241EAI+ -40°C to +85°C 28 SSOP A28+1 MAX3241EWI+ -40°C to +85°C 28 SO W28+6 MAX3222/MAX3232/MAX3237/MAX3241 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors 16 ______________________________________________________________________________________ ___________________Chip Topography ___________________Chip Information T2IN T1IN 0.127" (3.225mm) 0.087" (2.209mm) T2OUT R2IN R2OUT R1OUT R1IN T1OUT V+ C1+ VCC SHDN EN C1- C2+ C2- VGND MAX3222 TRANSISTOR COUNT: 339 SUBSTRATE CONNECTED TO GND MAX3222 339 MAX3232 339 MAX3237 1212 MAX3241 894 PART TRANSISTOR COUNT 3.0V to 5.5V, Low-Power, up to 1Mbps, True RS-232 Transceivers Using Four 0.1μF External Capacitors Maxim cannot assume responsibility for use of any circuitry other than circuitry entirely embodied in a Maxim product. No circuit patent licenses are implied. Maxim reserves the right to change the circuitry and specifications without notice at any time. Maxim Integrated Products, 120 San Gabriel Drive, Sunnyvale, CA 94086 408-737-7600 ____________________ 17 © 2007 Maxim Integrated Products Maxim is a registered trademark of Maxim Integrated Products, Inc. MAX3222/MAX3232/MAX3237/MAX3241 TSSOP4.40mm.EPS Package Information (The package drawing(s) in this data sheet may not reflect the most current specifications. For the latest package outline information, go to www.maxim-ic.com/packages.) Revision History Pages changed at Rev 7: 1, 15, 16, 17 Precision Micropower, Low Dropout Voltage References Data Sheet REF19x Series Rev. L Information furnished by Analog Devices is believed to be accurate and reliable. However, no responsibility is assumed by Analog Devices for its use, nor for any infringements of patents or other rights of third parties that may result from its use. Specifications subject to change without notice. No license is granted by implication or otherwise under any patent or patent rights of Analog Devices. Trademarks and registered trademarks are the property of their respective owners. One Technology Way, P.O. Box 9106, Norwood, MA 02062-9106, U.S.A. Tel: 781.329.4700 www.analog.com Fax: 781.461.3113 ©1996–2011 Analog Devices, Inc. All rights reserved. FEATURES Temperature coefficient: 5 ppm/°C maximum High output current: 30 mA Low supply current: 45 μA maximum Initial accuracy: ±2 mV maximum1 Sleep mode: 15 μA maximum Low dropout voltage Load regulation: 4 ppm/mA Line regulation: 4 ppm/V Short-circuit protection APPLICATIONS Portable instruments ADCs and DACs Smart sensors Solar powered applications Loop-current-powered instruments GENERAL DESCRIPTION The REF19x series precision band gap voltage references use a patented temperature drift curvature correction circuit and laser trimming of highly stable, thin-film resistors to achieve a very low temperature coefficient and high initial accuracy. The REF19x series is made up of micropower, low dropout voltage (LDV) devices, providing stable output voltage from supplies as low as 100 mV above the output voltage and consuming less than 45 μA of supply current. In sleep mode, which is enabled by applying a low TTL or CMOS level to the SLEEP pin, the output is turned off and supply current is further reduced to less than 15 μA. The REF19x series references are specified over the extended industrial temperature range (−40°C to +85°C) with typical performance specifications over −40°C to +125°C for applications, such as automotive. All electrical grades are available in an 8-lead SOIC package; the PDIP and TSSOP packages are available only in the lowest electrical grade. TEST PINS Test Pin 1 and Test Pin 5 are reserved for in-package Zener zap. To achieve the highest level of accuracy at the output, the Zener zapping technique is used to trim the output voltage. Because each unit may require a different amount of adjustment, the resistance value at the test pins varies widely from pin to pin and from part to part. The user should leave Pin 1 and Pin 5 unconnected. REF19xSERIESTOP VIEW(Not to Scale)TP1VS2SLEEP3GND4NCNCOUTPUTTP876500371-001NOTES1.NC = NO CONNECT.2.TP PINS ARE FACTORY TESTPOINTS, NO USER CONNECTION. Figure 1. 8-Lead SOIC_N and TSSOP Pin Configuration (S Suffix and RU Suffix) REF19xSERIESTOP VIEW(Not to Scale)TP1VS2SLEEP3GND4NCNCOUTPUTTP876500371-002NOTES1.NC = NO CONNECT.2.TP PINS ARE FACTORY TESTPOINTS, NO USER CONNECTION. Figure 2. 8-Lead PDIP Pin Configuration (P Suffix) Table 1. Nominal Output Voltage Part Number Nominal Output Voltage (V) REF191 2.048 REF192 2.50 REF193 3.00 REF194 4.50 REF195 5.00 REF196 3.30 REF198 4.096 1 Initial accuracy does not include shift due to solder heat effect (see the Applications Information section). FEATURES l VERY LOW NOISE: 4.5nV/ÖHz at 10kHz l FAST SETTLING TIME: OPA627—550ns to 0.01% OPA637—450ns to 0.01% l LOW VOS: 100mV max l LOW DRIFT: 0.8mV/°C max l LOW IB: 5pA max l OPA627: Unity-Gain Stable l OPA637: Stable in Gain ³ 5 OPA627 OPA637 DESCRIPTION The OPA627 and OPA637 Difet operational amplifiers provide a new level of performance in a precision FET op amp. When compared to the popular OPA111 op amp, the OPA627/637 has lower noise, lower offset voltage, and much higher speed. It is useful in a broad range of precision and high speed analog circuitry. The OPA627/637 is fabricated on a high-speed, dielectrically- isolated complementary NPN/PNP process. It operates over a wide range of power supply voltage— ±4.5V to ±18V. Laser-trimmed Difet input circuitry provides high accuracy and low-noise performance comparable with the best bipolar-input op amps. High frequency complementary transistors allow increased circuit bandwidth, attaining dynamic performance not possible with previous precision FET op amps. The OPA627 is unity-gain stable. The OPA637 is stable in gains equal to or greater than five. Difet fabrication achieves extremely low input bias currents without compromising input voltage noise performance. Low input bias current is maintained over a wide input common-mode voltage range with unique cascode circuitry. The OPA627/637 is available in plastic DIP, SOIC and metal TO-99 packages. Industrial and military temperature range models are available. Difet ®, Burr-Brown Corp. ® Precision High-Speed Difet ® OPERATIONAL AMPLIFIERS APPLICATIONS l PRECISION INSTRUMENTATION l FAST DATA ACQUISITION l DAC OUTPUT AMPLIFIER l OPTOELECTRONICS l SONAR, ULTRASOUND l HIGH-IMPEDANCE SENSOR AMPS l HIGH-PERFORMANCE AUDIO CIRCUITRY l ACTIVE FILTERS Trim 5 Trim 1 +In 3 –In 2 Output 6 7 +VS –VS 4 ©1989 Burr-Brown Corporation PDS-998H Printed in U.S.A. March, 1998 International Airport Industrial Park • Mailing Address: PO Box 11400, Tucson, AZ 85734 • Street Address: 6730 S. Tucson Blvd., Tucson, AZ 85706 • Tel: (520) 746-1111 • Twx: 910-952-1111 Internet: http://www.burr-brown.com/ • FAXLine: (800) 548-6133 (US/Canada Only) • Cable: BBRCORP • Telex: 066-6491 • FAX: (520) 889-1510 • Immediate Product Info: (800) 548-6132 OPA627 OPA627 SBOS165 2 ® OPA627, 637 SPECIFICATIONS ELECTRICAL At TA = +25°C, and VS = ±15V, unless otherwise noted. OPA627BM, BP, SM OPA627AM, AP, AU OPA637BM, BP, SM OPA637AM, AP, AU PARAMETER CONDITIONS MIN TYP MAX MIN TYP MAX UNITS OFFSET VOLTAGE (1) Input Offset Voltage 40 100 130 250 mV AP, BP, AU Grades 100 250 280 500 mV Average Drift 0.4 0.8 1.2 2 mV/°C AP, BP, AU Grades 0.8 2 2.5 mV/°C Power Supply Rejection VS = ±4.5 to ±18V 106 120 100 116 dB INPUT BIAS CURRENT (2) Input Bias Current VCM = 0V 1 5 2 10 pA Over Specified Temperature VCM = 0V 1 2 nA SM Grade VCM = 0V 50 nA Over Common-Mode Voltage VCM = ±10V 1 2 pA Input Offset Current VCM = 0V 0.5 5 1 10 pA Over Specified Temperature VCM = 0V 1 2 nA SM Grade 50 nA NOISE Input Voltage Noise Noise Density: f = 10Hz 15 40 20 nV/ÖHz f = 100Hz 8 20 10 nV/ÖHz f = 1kHz 5.2 8 5.6 nV/ÖHz f = 10kHz 4.5 6 4.8 nV/ÖHz Voltage Noise, BW = 0.1Hz to 10Hz 0.6 1.6 0.8 mVp-p Input Bias Current Noise Noise Density, f = 100Hz 1.6 2.5 2.5 fA/ÖHz Current Noise, BW = 0.1Hz to 10Hz 30 60 48 fAp-p INPUT IMPEDANCE Differential 1013 || 8 * W || pF Common-Mode 1013 || 7 * W || pF INPUT VOLTAGE RANGE Common-Mode Input Range ±11 ±11.5 * * V Over Specified Temperature ±10.5 ±11 * * V Common-Mode Rejection VCM = ±10.5V 106 116 100 110 dB OPEN-LOOP GAIN Open-Loop Voltage Gain VO = ±10V, RL = 1kW 112 120 106 116 dB Over Specified Temperature VO = ±10V, RL = 1kW 106 117 100 110 dB SM Grade VO = ±10V, RL = 1kW 100 114 dB FREQUENCY RESPONSE Slew Rate: OPA627 G = –1, 10V Step 40 55 * * V/ms OPA637 G = –4, 10V Step 100 135 * * V/ms Settling Time: OPA627 0.01% G = –1, 10V Step 550 * ns 0.1% G = –1, 10V Step 450 * ns OPA637 0.01% G = –4, 10V Step 450 * ns 0.1% G = –4, 10V Step 300 * ns Gain-Bandwidth Product: OPA627 G = 1 16 * MHz OPA637 G = 10 80 * MHz Total Harmonic Distortion + Noise G = +1, f = 1kHz 0.00003 * % POWER SUPPLY Specified Operating Voltage ±15 * V Operating Voltage Range ±4.5 ±18 * * V Current ±7 ±7.5 * * mA OUTPUT Voltage Output RL = 1kW ±11.5 ±12.3 * * Over Specified Temperature ±11 ±11.5 * * V Current Output VO = ±10V ±45 * mA Short-Circuit Current ±35 +70/–55 ±100 * * * mA Output Impedance, Open-Loop 1MHz 55 * W TEMPERATURE RANGE Specification: AP, BP, AM, BM, AU –25 +85 * * °C SM –55 +125 °C Storage: AM, BM, SM –60 +150 * * °C AP, BP, AU –40 +125 * * °C qJ-A: AM, BM, SM 200 * °C/W AP, BP 100 * °C/W AU 160 °C/W * Specifications same as “B” grade. NOTES: (1) Offset voltage measured fully warmed-up. (2) High-speed test at TJ = +25°C. See Typical Performance Curves for warmed-up performance. The information provided herein is believed to be reliable; however, BURR-BROWN assumes no responsibility for inaccuracies or omissions. BURR-BROWN assumes no responsibility for the use of this information, and all use of such information shall be entirely at the user’s own risk. Prices and specifications are subject to change without notice. No patent rights or licenses to any of the circuits described herein are implied or granted to any third party. BURR-BROWN does not authorize or warrant any BURR-BROWN product for use in life support devices and/or systems. 3 ® OPA627, 637 PIN CONFIGURATIONS Top View DIP/SOIC Offset Trim –In +In –V No Internal Connection +V Output S Offset Trim S 1 2 3 4 8 7 6 5 Top View TO-99 Offset Trim –In Output +In Offset Trim –VS +VS No Internal Connection Case connected to –VS. 8 1 2 3 4 5 6 7 ABSOLUTE MAXIMUM RATINGS(1) Supply Voltage .................................................................................. ±18V Input Voltage Range .............................................. +VS + 2V to –VS – 2V Differential Input Range ....................................................... Total VS + 4V Power Dissipation ........................................................................ 1000mW Operating Temperature M Package .................................................................. –55°C to +125°C P, U Package ............................................................. –40°C to +125°C Storage Temperature M Package .................................................................. –65°C to +150°C P, U Package ............................................................. –40°C to +125°C Junction Temperature M Package .................................................................................. +175°C P, U Package ............................................................................. +150°C Lead Temperature (soldering, 10s) ............................................... +300°C SOlC (soldering, 3s) ................................................................... +260°C NOTE: (1) Stresses above these ratings may cause permanent damage. ELECTROSTATIC DISCHARGE SENSITIVITY This integrated circuit can be damaged by ESD. Burr-Brown recommends that all integrated circuits be handled with appropriate precautions. Failure to observe proper handling and installation procedures can cause damage. ESD damage can range from subtle performance degradation to complete device failure. Precision integrated circuits may be more susceptible to damage because very small parametric changes could cause the device not to meet its published specifications. PACKAGE/ORDERING INFORMATION PACKAGE DRAWING TEMPERATURE PRODUCT PACKAGE NUMBER(1) RANGE OPA627AP Plastic DIP 006 –25°C to +85°C OPA627BP Plastic DIP 006 –25°C to +85°C OPA627AU SOIC 182 –25°C to +85°C OPA627AM TO-99 Metal 001 –25°C to +85°C OPA627BM TO-99 Metal 001 –25°C to +85°C OPA627SM TO-99 Metal 001 –55°C to +125°C OPA637AP Plastic DIP 006 –25°C to +85°C OPA637BP Plastic DIP 006 –25°C to +85°C OPA637AU SOIC 182 –25°C to +85°C OPA637AM TO-99 Metal 001 –25°C to +85°C OPA637BM TO-99 Metal 001 –25°C to +85°C OPA637SM TO-99 Metal 001 –55°C to +125°C NOTE: (1) For detailed drawing and dimension table, please see end of data sheet, or Appendix C of Burr-Brown IC Data Book. 4 ® OPA627, 637 TYPICAL PERFORMANCE CURVES At TA = +25°C, and VS = ±15V, unless otherwise noted. INPUT VOLTAGE NOISE SPECTRAL DENSITY 1k 100 10 1 1 Frequency (Hz) Voltage Noise (nV/ Ö Hz) 10 100 1k 10k 100k 1M 10M VOLTAGE NOISE vs SOURCE RESISTANCE Source Resistance ( W ) 1k 100 10 1 100 OPA627 + Resistor Resistor Noise Only Spot Noise at 10kHz Voltage Noise (nV/ Ö Hz) 1k 10k 100k 1M 10M 100M Comparison with OPA27 Bipolar Op Amp + Resistor – + RS OPA627 GAIN/PHASE vs FREQUENCY Phase (Degrees) Gain (dB) 30 20 10 0 –10 –90 –120 –150 –180 –210 1 Phase Gain Frequency (MHz) 10 100 75° Phase Margin OPA637 GAIN/PHASE vs FREQUENCY Phase (Degrees) Gain (dB) 30 20 10 0 –10 –90 –120 –150 –180 –210 1 10 100 Phase Gain Frequency (MHz) TOTAL INPUT VOLTAGE NOISE vs BANDWIDTH 100 10 1 0.1 0.01 1 10 100 1k 10k 100k 1M 10M Bandwidth (Hz) Input Voltage Noise (μV) Noise Bandwidth: 0.1Hz to indicated frequency. RMS p-p OPEN-LOOP GAIN vs FREQUENCY Frequency (Hz) Voltage Gain (dB) 1 10 100 1k 10k 100k 1M 10M 100M 140 120 100 80 60 40 20 0 –20 OPA637 OPA627 5 ® OPA627, 637 TYPICAL PERFORMANCE CURVES (CONT) At TA = +25°C, and VS = ±15V, unless otherwise noted. OPEN-LOOP GAIN vs TEMPERATURE Voltage Gain (dB) Temperature (°C) 125 120 115 110 105 –75 –50 –25 0 25 50 75 100 125 OPEN-LOOP OUTPUT IMPEDANCE vs FREQUENCY Frequency (Hz) Output Resistance (W) 100 80 60 40 20 0 2 20 200 2k 20k 200k 2M 20M COMMON-MODE REJECTION vs FREQUENCY Frequency (Hz) Common-Mode Rejection Ratio (dB) 140 120 100 80 60 40 20 0 1 10 100 1k 10k 100k 1M 10M OPA627 OPA637 COMMON-MODE REJECTION vs INPUT COMMON MODE VOLTAGE 130 120 110 100 90 80 Common-Mode Rejection (dB) Common-Mode Voltage (V) –15 –10 –5 0 5 10 15 POWER-SUPPLY REJECTION vs FREQUENCY Frequency (Hz) Power-Supply Rejection (dB) 140 120 100 80 60 40 20 0 1 –VS PSRR 627 and 637 +VS PSRR 627 637 10 100 1k 10k 100k 1M 10M POWER-SUPPLY REJECTION AND COMMON-MODE REJECTION vs TEMPERATURE Temperature (°C) CMR and PSR (dB) 125 120 115 110 105 –75 PSR CMR –50 –25 0 25 50 75 100 125 6 ® OPA627, 637 TYPICAL PERFORMANCE CURVES (CONT) At TA = +25°C, and VS = ±15V, unless otherwise noted. SUPPLY CURRENT vs TEMPERATURE Temperature (°C) Supply Current (mA) 8 7.5 7 6.5 6 –75 –50 –25 0 25 50 75 100 125 OUTPUT CURRENT LIMIT vs TEMPERATURE Output Current (mA) 100 80 60 40 20 0 –75 –50 –25 0 25 50 75 100 125 Temperature (°C) –IL at VO = –10V –IL at VO = 0V +IL at VO = +10V +IL at VO = 0V OPA627 GAIN-BANDWIDTH AND SLEW RATE vs TEMPERATURE Temperature (°C) Gain-Bandwidth (MHz) 24 20 16 12 8 –75 Slew Rate GBW 60 55 50 Slew Rate (V/μs) –50 –25 0 25 50 75 100 125 OPA637 GAIN-BANDWIDTH AND SLEW RATE vs TEMPERATURE Temperature (°C) Gain-Bandwidth (MHz) 120 100 80 60 40 –75 Slew Rate (V/μs) 160 140 120 100 80 Slew Rate GBW –50 –25 0 25 50 75 100 125 OPA627 TOTAL HARMONIC DISTORTION + NOISE vs FREQUENCY Frequency (Hz) THD+N (%) 20 100 1k 10k 20k 0.1 0.01 0.001 0.0001 0.00001 G = +10 G = +1 Measurement BW: 80kHz – + – + 100pF 100pF G = +1 G = +10 VI VI 549 5k 600 600 VO = ±10V VO = ±10V W W W W OPA637 TOTAL HARMONIC DISTORTION + NOISE vs FREQUENCY Frequency (Hz) THD+N (%) 20 100 1k 10k 20k 1 0.1 0.01 0.001 0.0001 G = +10 G = +50 – + 100pF G = +10 VI 549 5k 600 VO = ±10V W W W – + 100pF G = +50 VI 102 5k 600 VO = ±10V W W W Measurement BW: 80kHz 7 ® OPA627, 637 TYPICAL PERFORMANCE CURVES (CONT) At TA = +25°C, and VS = ±15V, unless otherwise noted. INPUT BIAS AND OFFSET CURRENT vs JUNCTION TEMPERATURE Junction Temperature (°C) Input Current (pA) 10k 1k 100 10 1 0.1 –50 –25 0 25 50 75 100 125 150 IB IOS INPUT BIAS CURRENT vs POWER SUPPLY VOLTAGE Supply Voltage (±VS) Input Bias Current (pA) 20 15 10 5 0 ±4 ±6 ±8 ±10 ±12 ±14 ±16 ±18 NOTE: Measured fully warmed-up. TO-99 with 0807HS Heat Sink TO-99 Plastic DIP, SOIC INPUT BIAS CURRENT vs COMMON-MODE VOLTAGE Common-Mode Voltage (V) Input Bias Current Multiplier 1.2 1.1 1 0.9 0.8 –15 –10 –5 0 5 10 15 Beyond Linear Common-Mode Range Beyond Linear Common-Mode Range INPUT OFFSET VOLTAGE WARM-UP vs TIME Time From Power Turn-On (Min) Offset Voltage Change (μV) 50 25 0 –25 –50 0 1 2 3 4 5 6 MAX OUTPUT VOLTAGE vs FREQUENCY Frequency (Hz) Output Voltage (Vp-p) 30 20 10 0 100k 1M 10M 100M OPA627 OPA637 SETTLING TIME vs CLOSED-LOOP GAIN 100 10 1 0.1 –1 –10 –100 –1000 Closed-Loop Gain (V/V) Settling Time (μs) Error Band: ±0.01% OPA637 OPA627 8 ® OPA627, 637 TYPICAL PERFORMANCE CURVES (CONT) At TA = +25°C, and VS = ±15V, unless otherwise noted. FIGURE 1. Circuits with Noise Gain Less than Five Require the OPA627 for Proper Stability. SETTLING TIME vs ERROR BAND 1500 1000 500 0 0.001 0.01 0.1 1 10 Error Band (%) Settling Time (ns) OPA637 G = –4 OPA627 G = –1 – + CF RI RF 2kW +5V –5V OPA627 OPA637 RI 2kW 500W RF 2kW 2kW CF 6pF 4pF SETTLING TIME vs LOAD CAPACITANCE 0 150 200 300 400 500 Load Capacitance (pF) 3 2 1 0 Settling Time (μs) Error Band: ±0.01% OPA637 G = –4 OPA627 G = –1 APPLICATIONS INFORMATION The OPA627 is unity-gain stable. The OPA637 may be used to achieve higher speed and bandwidth in circuits with noise gain greater than five. Noise gain refers to the closed-loop gain of a circuit as if the non-inverting op amp input were being driven. For example, the OPA637 may be used in a non-inverting amplifier with gain greater than five, or an inverting amplifier of gain greater than four. When choosing between the OPA627 or OPA637, it is important to consider the high frequency noise gain of your circuit configuration. Circuits with a feedback capacitor (Figure 1) place the op amp in unity noise-gain at high frequency. These applications must use the OPA627 for proper stability. An exception is the circuit in Figure 2, where a small feedback capacitance is used to compensate for the input capacitance at the op amp’s inverting input. In this case, the closed-loop noise gain remains constant with frequency, so if the closed-loop gain is equal to five or greater, the OPA637 may be used. – + – + – + – + – + – + Buffer Bandwidth Limiting Integrator Filter RI RF < 4R Inverting Amp G < |–4| RI RF < 4RI Non-Inverting Amp G < 5 OPA627 OPA627 OPA627 OPA627 OPA627 OPA627 9 ® OPA627, 637 – + C2 C1 R2 R1 OPA637 C1 = CIN + CSTRAY C2 = R1 C1 R2 OFFSET VOLTAGE ADJUSTMENT The OPA627/637 is laser-trimmed for low offset voltage and drift, so many circuits will not require external adjustment. Figure 3 shows the optional connection of an external potentiometer to adjust offset voltage. This adjustment should not be used to compensate for offsets created elsewhere in a system (such as in later amplification stages or in an A/D converter) because this could introduce excessive temperature drift. Generally, the offset drift will change by approximately 4mV/°C for 1mV of change in the offset voltage due to an offset adjustment (as shown on Figure 3). FIGURE 2. Circuits with Noise Gain Equal to or Greater than Five May Use the OPA637. amp contributes little additional noise. Below 1kW, op amp noise dominates over the resistor noise, but compares favorably with precision bipolar op amps. CIRCUIT LAYOUT As with any high speed, wide bandwidth circuit, careful layout will ensure best performance. Make short, direct interconnections and avoid stray wiring capacitance—especially at the input pins and feedback circuitry. The case (TO-99 metal package only) is internally connected to the negative power supply as it is with most common op amps. Pin 8 of the plastic DIP, SOIC, and TO-99 packages has no internal connection. Power supply connections should be bypassed with good high frequency capacitors positioned close to the op amp pins. In most cases 0.1mF ceramic capacitors are adequate. The OPA627/637 is capable of high output current (in excess of 45mA). Applications with low impedance loads or capacitive loads with fast transient signals demand large currents from the power supplies. Larger bypass capacitors such as 1mF solid tantalum capacitors may improve dynamic performance in these applications. NOISE PERFORMANCE Some bipolar op amps may provide lower voltage noise performance, but both voltage noise and bias current noise contribute to the total noise of a system. The OPA627/637 is unique in providing very low voltage noise and very low current noise. This provides optimum noise performance over a wide range of sources, including reactive source impedances. This can be seen in the performance curve showing the noise of a source resistor combined with the noise of an OPA627. Above a 2kW source resistance, the op FIGURE 4. Connection of Input Guard for Lowest IB. Board Layout for Input Guarding: Guard top and bottom of board. Alternate—use Teflon® standoff for sensitive input pins. Teflon® E.I. du Pont de Nemours & Co. – + 2 In 3 Non-inverting 6 OPA627 Out – + 2 3 In Inverting 6 OPA627 Out – + 2 In 3 Buffer 6 OPA627 Out 3 2 4 5 6 7 8 No Internal Connection 1 TO-99 Bottom View To Guard Drive – + 2 3 7 1 5 6 +VS –VS OPA627/637 100kW 10kW to 1MW Potentiometer (100kW preferred) ±10mV Typical Trim Range 4 FIGURE 3. Optional Offset Voltage Trim Circuit. 10 ® OPA627, 637 takes approximately 500ns. When the output is driven into the positive limit, recovery takes approximately 6ms. Output recovery of the OPA627 can be improved using the output clamp circuit shown in Figure 5. Diodes at the inverting input prevent degradation of input bias current. INPUT BIAS CURRENT Difet fabrication of the OPA627/637 provides very low input bias current. Since the gate current of a FET doubles approximately every 10°C, to achieve lowest input bias current, the die temperature should be kept as low as possible. The high speed and therefore higher quiescent current of the OPA627/637 can lead to higher chip temperature. A simple press-on heat sink such as the Burr-Brown model 807HS (TO-99 metal package) can reduce chip temperature by approximately 15°C, lowering the IB to one-third its warmed-up value. The 807HS heat sink can also reduce lowfrequency voltage noise caused by air currents and thermoelectric effects. See the data sheet on the 807HS for details. Temperature rise in the plastic DIP and SOIC packages can be minimized by soldering the device to the circuit board. Wide copper traces will also help dissipate heat. The OPA627/637 may also be operated at reduced power supply voltage to minimize power dissipation and temperature rise. Using ±5V power supplies reduces power dissipation to one-third of that at ±15V. This reduces the IB of TO- 99 metal package devices to approximately one-fourth the value at ±15V. Leakage currents between printed circuit board traces can easily exceed the input bias current of the OPA627/637. A circuit board “guard” pattern (Figure 4) reduces leakage effects. By surrounding critical high impedance input circuitry with a low impedance circuit connection at the same potential, leakage current will flow harmlessly to the lowimpedance node. The case (TO-99 metal package only) is internally connected to –VS. Input bias current may also be degraded by improper handling or cleaning. Contamination from handling parts and circuit boards may be removed with cleaning solvents and deionized water. Each rinsing operation should be followed by a 30-minute bake at 85°C. Many FET-input op amps exhibit large changes in input bias current with changes in input voltage. Input stage cascode circuitry makes the input bias current of the OPA627/637 virtually constant with wide common-mode voltage changes. This is ideal for accurate high inputimpedance buffer applications. PHASE-REVERSAL PROTECTION The OPA627/637 has internal phase-reversal protection. Many FET-input op amps exhibit a phase reversal when the input is driven beyond its linear common-mode range. This is most often encountered in non-inverting circuits when the input is driven below –12V, causing the output to reverse into the positive rail. The input circuitry of the OPA627/637 does not induce phase reversal with excessive commonmode voltage, so the output limits into the appropriate rail. OUTPUT OVERLOAD When the inputs to the OPA627/637 are overdriven, the output voltage of the OPA627/637 smoothly limits at approximately 2.5V from the positive and negative power supplies. If driven to the negative swing limit, recovery +VS 5kW (2) HP 5082-2811 1kW 5kW –VS VO Diode Bridge BB: PWS740-3 ZD1 : 10V IN961 Clamps output at VO = ±11.5V RI VI – + RF ZD1 OPA627 FIGURE 5. Clamp Circuit for Improved Overload Recovery. CAPACITIVE LOADS As with any high-speed op amp, best dynamic performance can be achieved by minimizing the capacitive load. Since a load capacitance presents a decreasing impedance at higher frequency, a load capacitance which is easily driven by a slow op amp can cause a high-speed op amp to perform poorly. See the typical curves showing settling times as a function of capacitive load. The lower bandwidth of the OPA627 makes it the better choice for driving large capacitive loads. Figure 6 shows a circuit for driving very large load capacitance. This circuit’s two-pole response can also be used to sharply limit system bandwidth. This is often useful in reducing the noise of systems which do not require the full bandwidth of the OPA627. FIGURE 6. Driving Large Capacitive Loads. R1 – + RF 1kW OPA627 CF G = +1 BW 1MHz 200pF For Approximate Butterworth Response: CF = 2 RO CL RF RF >> RO G = 1+ RF R1 ³ Optional Gain Gain > 1 f–3dB = 1 2p Ö RF RO CF CL CL 5nF RO 20W 11 ® OPA627, 637 INPUT PROTECTION The inputs of the OPA627/637 are protected for voltages between +VS + 2V and –VS – 2V. If the input voltage can exceed these limits, the amplifier should be protected. The diode clamps shown in Figure 7a will prevent the input voltage from exceeding one forward diode voltage drop beyond the power supplies—well within the safe limits. If the input source can deliver current in excess of the maximum forward current of the protection diodes, use a series resistor, RS, to limit the current. Be aware that adding resistance to the input will increase noise. The 4nV/ÖHz theoretical thermal noise of a 1kW resistor will add to the 4.5nV/ÖHz noise of the OPA627/637 (by the square-root of the sum of the squares), producing a total noise of 6nV/ÖHz. Resistors below 100W add negligible noise. Leakage current in the protection diodes can increase the total input bias current of the circuit. The specified maximum leakage current for commonly used diodes such as the 1N4148 is approximately 25nA—more than a thousand times larger than the input bias current of the OPA627/637. Leakage current of these diodes is typically much lower and may be adequate in many applications. Light falling on the junction of the protection diodes can dramatically increase leakage current, so common glass-packaged diodes should be shielded from ambient light. Very low leakage can be achieved by using a diode-connected FET as shown. The 2N4117A is specified at 1pA and its metal case shields the junction from light. Sometimes input protection is required on I/V converters of inverting amplifiers (Figure 7b). Although in normal operation, the voltage at the summing junction will be near zero (equal to the offset voltage of the amplifier), large input transients may cause this node to exceed 2V beyond the power supplies. In this case, the summing junction should be protected with diode clamps connected to ground. Even with the low voltage present at the summing junction, common signal diodes may have excessive leakage current. Since the reverse voltage on these diodes is clamped, a diode-connected signal transistor can be used as an inexpensive low leakage diode (Figure 7b). FIGURE 7. Input Protection Circuits. – + –VS +VS Optional RS VO D: IN4148 — 25nA Leakage 2N4117A — 1pA Leakage (a) = – + IIN VO D D D (b) D D: 2N3904 = NC Siliconix OPA627 OPA627 FPO When used as a unity-gain buffer, large common-mode input voltage steps produce transient variations in input-stage currents. This causes the rising edge to be slower and falling edges to be faster than nominal slew rates observed in higher-gain circuits. (A) (B) LARGE SIGNAL RESPONSE SMALL SIGNAL RESPONSE FIGURE 8. OPA627 Dynamic Performance, G = +1. – + OPA627 G = 1 12 ® OPA627, 637 When driven with a very fast input step (left), common-mode transients cause a slight variation in input stage currents which will reduce output slew rate. If the input step slew rate is reduced (right), output slew rate will increase slightly. FIGURE 9. OPA627 Dynamic Performance, G = –1. NOTE: (1) Optimum value will depend on circuit board layout and stray capacitance at the inverting input. LARGE SIGNAL RESPONSE +10 0 –10 VOUT (V) +10 0 –10 (C) (D) OPA637 LARGE SIGNAL RESPONSE OPA637 SMALL SIGNAL RESPONSE FPO FIGURE 10. OPA637 Dynamic Response, G = 5. –10 0 +10 –100 0 +100 (E) (F) VOUT (V) – + OPA627 G = –1 2kW 2kW 6pF(1) VOUT – + OPA637 G = 5 2kW 500W 4pF(1) VOUT NOTE: (1) Optimum value will depend on circuit board layout and capacitance at inverting input. VOUT (V) VOUT (mV) 13 ® OPA627, 637 OPA627 OPA637 RI, R1 2kW 500W CF 6pF 4pF Error Band ±0.5mV ±0.2mV (0.01%) NOTE: CF is selected for best settling time performance depending on test fixture layout. Once optimum value is determined, a fixed capacitor may be used. FIGURE 12. High Speed Instrumentation Amplifier, Gain = 100. –In +In + – OPA637 Differential Voltage Gain = 1 + 2RF/RG 2 3 – + – + INA105 Differential Amplifier 1 6 5 Output Gain = 100 CMRR 116dB Bandwidth 1MHz OPA637 25kW 25kW 25kW 25kW Input Common-Mode Range = ±5V » » 3pF RF 5kW RF 5kW RG 101W – + ±5V Out +15V 2kW CF 2kW Error Out RI RI 51W –15V HP- 5082- 2835 High Quality Pulse Generator / FIGURE 11. Settling Time and Slew Rate Test Circuit. FIGURE 14. Composite Amplifier for Wide Bandwidth. This composite amplifier uses the OPA603 current-feedback op amp to provide extended bandwidth and slew rate at high closed-loop gain. The feedback loop is closed around the composite amp, preserving the precision input characteristics of the OPA627/637. Use separate power supply bypass capacitors for each op amp. GAIN A1 R1 R2 R3 R4 –3dB SLEW RATE (V/V) OP AMP (W) (kW) (W) (kW) (MHz) (V/ms) 100 OPA627 50.5(1) 4.99 20 1 15 700 1000 OPA637 49.9 4.99 12 1 11 500 NOTE: (1) Closest 1/2% value. *Minimize capacitance at this node. FIGURE 13. High Speed Instrumentation Amplifier, Gain = 1000. + – OPA603 – + A1 R3 R1 R4 R2 VI VO * RL ³ 150W for ±10V Out –In +In + – OPA637 Differential Voltage Gain = (1 + 2RF/RG) • 10 2 3 – + – + INA106 Differential Amplifier 1 6 5 Output Gain = 1000 CMRR 116dB Bandwidth 400kHz OPA637 10kW 10kW 100kW 100kW Input Common-Mode Range = ±10V » » 3pF RF 5kW RF 5kW RG 101W PACKAGE OPTION ADDENDUM www.ti.com 10-Jun-2014 Addendum-Page 1 PACKAGING INFORMATION Orderable Device Status (1) Package Type Package Drawing Pins Package Qty Eco Plan (2) Lead/Ball Finish (6) MSL Peak Temp (3) Op Temp (°C) Device Marking (4/5) Samples OPA627AM NRND TO-99 LMC 8 20 Green (RoHS & no Sb/Br) AU N / A for Pkg Type OPA627AM OPA627AP ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA627AP OPA627APG4 ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA627AP OPA627AU ACTIVE SOIC D 8 75 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 627AU OPA627AU/2K5 ACTIVE SOIC D 8 2500 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 627AU OPA627AU/2K5E4 ACTIVE SOIC D 8 2500 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 627AU OPA627AUE4 ACTIVE SOIC D 8 75 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 627AU OPA627AUG4 ACTIVE SOIC D 8 75 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 627AU OPA627BM NRND TO-99 LMC 8 1 Green (RoHS & no Sb/Br) AU N / A for Pkg Type OPA627BM OPA627BP ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA627BP OPA627BPG4 ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA627BP OPA627SM NRND TO-99 LMC 8 20 Green (RoHS & no Sb/Br) AU N / A for Pkg Type OPA627SM OPA637AM NRND TO-99 LMC 8 20 Green (RoHS & no Sb/Br) AU N / A for Pkg Type OPA637AM OPA637AM2 OBSOLETE TO-99 LMC 8 TBD Call TI Call TI OPA637AP ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA637AP OPA637APG4 ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA637AP OPA637AU ACTIVE SOIC D 8 75 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 637AU OPA637AU/2K5 ACTIVE SOIC D 8 2500 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 637AU OPA637AUE4 OBSOLETE SOIC D 8 TBD Call TI Call TI -25 to 85 PACKAGE OPTION ADDENDUM www.ti.com 10-Jun-2014 Addendum-Page 2 Orderable Device Status (1) Package Type Package Drawing Pins Package Qty Eco Plan (2) Lead/Ball Finish (6) MSL Peak Temp (3) Op Temp (°C) Device Marking (4/5) Samples OPA637AUG4 ACTIVE SOIC D 8 75 Green (RoHS & no Sb/Br) CU NIPDAU Level-3-260C-168 HR -25 to 85 OPA 637AU OPA637BM NRND TO-99 LMC 8 20 Green (RoHS & no Sb/Br) AU N / A for Pkg Type OPA637BM OPA637BM1 OBSOLETE TO-99 LMC 8 TBD Call TI Call TI OPA637BP ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA637BP OPA637BPG4 ACTIVE PDIP P 8 50 TBD Call TI Call TI OPA637BP OPA637SM NRND TO-99 LMC 8 20 Green (RoHS & no Sb/Br) AU N / A for Pkg Type OPA637SM (1) The marketing status values are defined as follows: ACTIVE: Product device recommended for new designs. LIFEBUY: TI has announced that the device will be discontinued, and a lifetime-buy period is in effect. NRND: Not recommended for new designs. Device is in production to support existing customers, but TI does not recommend using this part in a new design. PREVIEW: Device has been announced but is not in production. Samples may or may not be available. OBSOLETE: TI has discontinued the production of the device. (2) Eco Plan - The planned eco-friendly classification: Pb-Free (RoHS), Pb-Free (RoHS Exempt), or Green (RoHS & no Sb/Br) - please check http://www.ti.com/productcontent for the latest availability information and additional product content details. TBD: The Pb-Free/Green conversion plan has not been defined. Pb-Free (RoHS): TI's terms "Lead-Free" or "Pb-Free" mean semiconductor products that are compatible with the current RoHS requirements for all 6 substances, including the requirement that lead not exceed 0.1% by weight in homogeneous materials. Where designed to be soldered at high temperatures, TI Pb-Free products are suitable for use in specified lead-free processes. Pb-Free (RoHS Exempt): This component has a RoHS exemption for either 1) lead-based flip-chip solder bumps used between the die and package, or 2) lead-based die adhesive used between the die and leadframe. The component is otherwise considered Pb-Free (RoHS compatible) as defined above. Green (RoHS & no Sb/Br): TI defines "Green" to mean Pb-Free (RoHS compatible), and free of Bromine (Br) and Antimony (Sb) based flame retardants (Br or Sb do not exceed 0.1% by weight in homogeneous material) (3) MSL, Peak Temp. - The Moisture Sensitivity Level rating according to the JEDEC industry standard classifications, and peak solder temperature. (4) There may be additional marking, which relates to the logo, the lot trace code information, or the environmental category on the device. (5) Multiple Device Markings will be inside parentheses. Only one Device Marking contained in parentheses and separated by a "~" will appear on a device. If a line is indented then it is a continuation of the previous line and the two combined represent the entire Device Marking for that device. (6) Lead/Ball Finish - Orderable Devices may have multiple material finish options. Finish options are separated by a vertical ruled line. Lead/Ball Finish values may wrap to two lines if the finish value exceeds the maximum column width. PACKAGE OPTION ADDENDUM www.ti.com 10-Jun-2014 Addendum-Page 3 Important Information and Disclaimer:The information provided on this page represents TI's knowledge and belief as of the date that it is provided. TI bases its knowledge and belief on information provided by third parties, and makes no representation or warranty as to the accuracy of such information. Efforts are underway to better integrate information from third parties. TI has taken and continues to take reasonable steps to provide representative and accurate information but may not have conducted destructive testing or chemical analysis on incoming materials and chemicals. TI and TI suppliers consider certain information to be proprietary, and thus CAS numbers and other limited information may not be available for release. In no event shall TI's liability arising out of such information exceed the total purchase price of the TI part(s) at issue in this document sold by TI to Customer on an annual basis. TAPE AND REEL INFORMATION *All dimensions are nominal Device Package Type Package Drawing Pins SPQ Reel Diameter (mm) Reel Width W1 (mm) A0 (mm) B0 (mm) K0 (mm) P1 (mm) W (mm) Pin1 Quadrant OPA627AU/2K5 SOIC D 8 2500 330.0 12.4 6.4 5.2 2.1 8.0 12.0 Q1 OPA637AU/2K5 SOIC D 8 2500 330.0 12.4 6.4 5.2 2.1 8.0 12.0 Q1 PACKAGE MATERIALS INFORMATION www.ti.com 26-Jan-2013 Pack Materials-Page 1 *All dimensions are nominal Device Package Type Package Drawing Pins SPQ Length (mm) Width (mm) Height (mm) OPA627AU/2K5 SOIC D 8 2500 367.0 367.0 35.0 OPA637AU/2K5 SOIC D 8 2500 367.0 367.0 35.0 PACKAGE MATERIALS INFORMATION www.ti.com 26-Jan-2013 Pack Materials-Page 2 IMPORTANT NOTICE Texas Instruments Incorporated and its subsidiaries (TI) reserve the right to make corrections, enhancements, improvements and other changes to its semiconductor products and services per JESD46, latest issue, and to discontinue any product or service per JESD48, latest issue. Buyers should obtain the latest relevant information before placing orders and should verify that such information is current and complete. All semiconductor products (also referred to herein as “components”) are sold subject to TI’s terms and conditions of sale supplied at the time of order acknowledgment. TI warrants performance of its components to the specifications applicable at the time of sale, in accordance with the warranty in TI’s terms and conditions of sale of semiconductor products. Testing and other quality control techniques are used to the extent TI deems necessary to support this warranty. Except where mandated by applicable law, testing of all parameters of each component is not necessarily performed. TI assumes no liability for applications assistance or the design of Buyers’ products. Buyers are responsible for their products and applications using TI components. To minimize the risks associated with Buyers’ products and applications, Buyers should provide adequate design and operating safeguards. TI does not warrant or represent that any license, either express or implied, is granted under any patent right, copyright, mask work right, or other intellectual property right relating to any combination, machine, or process in which TI components or services are used. Information published by TI regarding third-party products or services does not constitute a license to use such products or services or a warranty or endorsement thereof. Use of such information may require a license from a third party under the patents or other intellectual property of the third party, or a license from TI under the patents or other intellectual property of TI. Reproduction of significant portions of TI information in TI data books or data sheets is permissible only if reproduction is without alteration and is accompanied by all associated warranties, conditions, limitations, and notices. TI is not responsible or liable for such altered documentation. Information of third parties may be subject to additional restrictions. Resale of TI components or services with statements different from or beyond the parameters stated by TI for that component or service voids all express and any implied warranties for the associated TI component or service and is an unfair and deceptive business practice. TI is not responsible or liable for any such statements. Buyer acknowledges and agrees that it is solely responsible for compliance with all legal, regulatory and safety-related requirements concerning its products, and any use of TI components in its applications, notwithstanding any applications-related information or support that may be provided by TI. Buyer represents and agrees that it has all the necessary expertise to create and implement safeguards which anticipate dangerous consequences of failures, monitor failures and their consequences, lessen the likelihood of failures that might cause harm and take appropriate remedial actions. Buyer will fully indemnify TI and its representatives against any damages arising out of the use of any TI components in safety-critical applications. In some cases, TI components may be promoted specifically to facilitate safety-related applications. With such components, TI’s goal is to help enable customers to design and create their own end-product solutions that meet applicable functional safety standards and requirements. Nonetheless, such components are subject to these terms. No TI components are authorized for use in FDA Class III (or similar life-critical medical equipment) unless authorized officers of the parties have executed a special agreement specifically governing such use. Only those TI components which TI has specifically designated as military grade or “enhanced plastic” are designed and intended for use in military/aerospace applications or environments. Buyer acknowledges and agrees that any military or aerospace use of TI components which have not been so designated is solely at the Buyer's risk, and that Buyer is solely responsible for compliance with all legal and regulatory requirements in connection with such use. TI has specifically designated certain components as meeting ISO/TS16949 requirements, mainly for automotive use. In any case of use of non-designated products, TI will not be responsible for any failure to meet ISO/TS16949. Products Applications Audio www.ti.com/audio Automotive and Transportation www.ti.com/automotive Amplifiers amplifier.ti.com Communications and Telecom www.ti.com/communications Data Converters dataconverter.ti.com Computers and Peripherals www.ti.com/computers DLP® Products www.dlp.com Consumer Electronics www.ti.com/consumer-apps DSP dsp.ti.com Energy and Lighting www.ti.com/energy Clocks and Timers www.ti.com/clocks Industrial www.ti.com/industrial Interface interface.ti.com Medical www.ti.com/medical Logic logic.ti.com Security www.ti.com/security Power Mgmt power.ti.com Space, Avionics and Defense www.ti.com/space-avionics-defense Microcontrollers microcontroller.ti.com Video and Imaging www.ti.com/video RFID www.ti-rfid.com OMAP Applications Processors www.ti.com/omap TI E2E Community e2e.ti.com Wireless Connectivity www.ti.com/wirelessconnectivity Mailing Address: Texas Instruments, Post Office Box 655303, Dallas, Texas 75265 Copyright © 2014, Texas Instruments Incorporated For free samples & the latest literature: http://www.maxim-ic.com, or phone 1-800-998-8800. For small orders, phone 1-800-835-8769. General Description The MAX4661/MAX4662/MAX4663 quad analog switches feature low on-resistance of 2.5½ max. On-resistance is matched between switches to 0.5W max and is flat (0.5W max) over the specified signal range. Each switch can handle Rail-to-Rail® analog signals. Offleakage current is only 5nA max at TA = +85°C. These analog switches are ideal in low-distortion applications and are the preferred solution over mechanical relays in automatic test equipment or applications where current switching is required. They have lower power requirements, use less board space, and are more reliable than mechanical relays. The MAX4661 has four normally closed (NC) switches, and the MAX4662 has four normally open (NO) switches. The MAX4663 has two NC and two NO switches, and features guaranteed break-before-make switching. These devices operate from a single +4.5V to +36V supply or from dual ±4.5V to ±20V supplies. A separate logic supply pin guarantees TTL/CMOS-logic compatibility when operating across the entire supply voltage range. Applications Reed Relay Replacement Avionics Test Equipment ADC Systems Communication Systems Sample-and-Hold Circuits PBX, PABX Systems Data Acquisition Systems Audio-Signal Routing Features © Low On-Resistance (2.5W max) © Guaranteed RON Match Between Channels (0.5W max) © Guaranteed RON Flatness over Specified Signal Range (0.5W max) © Rail-to-Rail Signal Handling © Guaranteed Break-Before-Make (MAX4663) © > 2kV ESD Protection per Method 3015.7 © +4.5V to +36V Single-Supply Operation ±4.5V to ±20V Dual-Supply Operation © TTL/CMOS-Compatible Control Inputs MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches ________________________________________________________________ Maxim Integrated Products 1 19-1516; Rev 0; 7/99 PART MAX4661CAE MAX4661CWE MAX4661CPE 0°C to +70°C 0°C to +70°C 0°C to +70°C TEMP. RANGE PIN-PACKAGE 16 SSOP 16 Wide SO 16 Plastic DIP Ordering Information continued at end of data sheet. Ordering Information MAX4661EAE -40°C to +85°C 16 SSOP MAX4661EWE -40°C to +85°C 16 Wide SO MAX4661EPE -40°C to +85°C 16 Plastic DIP SWITCHES SHOWN FOR LOGIC “0” INPUT SSOP/SO/DIP MAX4662 LOGIC SWITCH 0 1 OFF ON TOP VIEW SSOP/SO/DIP MAX4661 LOGIC SWITCH 0 1 ON OFF SSOP/SO/DIP MAX4663 LOGIC SWITCHES 1, 4 0 1 OFF ON SWITCHES 2, 3 ON OFF 16 15 14 13 12 11 10 9 1 2 3 4 5 6 7 8 IN2 COM2 NC2 V- V+ NO1 COM1 IN1 MAX4663 VL NC3 COM3 IN4 IN3 COM4 NO4 GND 16 15 14 13 12 11 10 9 1 2 3 4 5 6 7 8 IN2 COM2 NC2 V- V+ NC1 COM1 IN1 MAX4661 VL NC3 COM3 IN4 IN3 COM4 NC4 GND 16 15 14 13 12 11 10 9 1 2 3 4 5 6 7 8 IN2 COM2 NO2 V- V+ NO1 COM1 IN1 MAX4662 VL NO3 COM3 IN4 IN3 COM4 NO4 GND Pin Configurations/Functional Diagrams/Truth Tables Rail-to-Rail is a registered trademark of Nippon Motorola, Ltd. MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches 2 _______________________________________________________________________________________ ABSOLUTE MAXIMUM RATINGS Stresses beyond those listed under “Absolute Maximum Ratings” may cause permanent damage to the device. These are stress ratings only, and functional operation of the device at these or any other conditions beyond those indicated in the operational sections of the specifications is not implied. Exposure to absolute maximum rating conditions for extended periods may affect device reliability. V+ to GND ..............................................................-0.3V to +44V V- to GND ..............................................................+0.3V to -44V V+ to V-...................................................................-0.3V to +44V VL to GND .......................................(GND - 0.3V) to (V+ + 0.3V) All Other Pins to GND (Note 1) .............(V- - 0.3V) to (V+ + 0.3V) Continuous Current (COM_, NO_, NC_) ........................±200mA Peak Current (COM_, NO_, NC_) (pulsed at 1ms, 10% duty cycle)................................ ±300mA Continuous Power Dissipation (TA = +70°C) SSOP (derate 7.1mW/°C above +70°C) .......................571mW Wide SO (derate 9.52mW/°C above +70°C).................762mW Plastic DIP (derate 10.53mW/°C above +70°C) ...........842mW Operating Temperature Ranges MAX466_C_E ......................................................0°C to +70°C MAX466_E_E ....................................................-40°C to +85°C Storage Temperature Range .............................-65°C to +150°C Lead Temperature (soldering, 10sec) .............................+300°C -5 5 -5 5 -20 20 TA = TMIN to TMAX V TA = +25°C V- V+ VCOM_, VNO_, VNC_ Input Voltage Range (Note 3) IN_ = 0.8V, all others = 2.4V IN_ = 2.4V, all others = 0.8V ICOM_ = 10mA, VNO_ or VNC_ = ±10V TA = +25°C TA = +25°C TA = +25°C CONDITIONS Logic Input Voltage Low VIN_L 0.8 ½ 0.1 0.5 ÆRON COM_ to NO_ or NC_ On-Resistance Match Between Channels (Notes 3, 4) ½ 1.7 2.5 RON COM_ to NO or NC_ On-Resistance Logic Input Voltage High VIN_H 2.4 V IIN_L -0.5 0.001 0.5 Input Current with Input Voltage Low IIN_H -0.5 0.001 0.5 μA Input Current with Input Voltage High ½ 0.1 0.5 RFLAT(ON) COM_ to NO_ or NC_ On-Resistance Flatness (Notes 3, 5) nA -0.5 0.01 0.5 INO_, INC_ Off-Leakage Current (NO_ or NC_) (Note 6) nA -0.5 0.01 0.5 ICOM_(OFF) COM Off-Leakage Current (Note 6) nA -1 0.01 1 ICOM_(ON) COM On-Leakage Current (Note 6) PARAMETER SYMBOL MIN TYP MAX UNITS ICOM_ = 10mA, VNO_ or VNC_= ±10V ICOM_ = 10mA; VNO_ or VNC_ = -5V, 0, 5V TA = +25°C VCOM_ = ±10V, VNO_ or VNC_= –+ 10V VCOM_ = ±10V, VNO_ or VNC_ = –+ 10V VCOM_ = ±10V, VNO_ or VNC_= ±10V or floating TA = +25°C TA = TMIN to TMAX 2.7 0.6 TA = TMIN to TMAX 0.6 TA = TMIN to TMAX TA = TMIN to TMAX TA = TMIN to TMAX ELECTRICAL CHARACTERISTICS—Dual Supplies (V+ = +15V, V- = -15V, VL = +5V, VIN_H = +2.4V, VIN_L = +0.8V, TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) (Note 2) μA V ANALOG SWITCH LOGIC INPUT Note 1: Signals on NC_, NO_, COM_, or IN_ exceeding V+ or V- will be clamped by internal diodes. Limit forward diode current to maximum current rating. MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches _______________________________________________________________________________________ 3 VIN = 0 or 5V μA TA = +25°C -0.5 0.001 0.5 Positive Supply Current I+ TA = +25°C f = 1MHz, Figure 7 RL = 50½, CL = 5pF, f = 1MHz, Figure 6 TA = TMIN to TMAX -5 5 RL = 50½, CL = 5pF, f = 1MHz, Figure 5 VCOM_ = ±10V, Figure 3, TA = +25°C VIN = 0 or 5V TA = +25°C f = 1MHz, Figure 7 CONDITIONS Power-Supply Range ±4.5 ±20.0 V pF dB On-Capacitance CCOM f = 1MHz, Figure 8 250 pF COM_ Off-Capacitance CCOM 55 -0.5 0.001 0.5 Logic Supply Current IL -0.5 0.001 0.5 Negative Supply Current INC_ or NO_ Capacitance COFF pF Crosstalk (Note 8) VCT -59 Off-Isolation (Note 7) VISO -56 dB tOPEN 5 30 ns Break-Before-Make Time (MAX4663 only) -0.5 0.001 0.5 Ground Current IGND 130 275 100 175 PARAMETER SYMBOL MIN TYP MAX UNITS VIN = 0 or 5V VIN = 0 or 5V TA = +25°C TA = TMIN to TMAX -5 5 -5 5 TA = TMIN to TMAX -5 5 ELECTRICAL CHARACTERISTICS—Dual Supplies (continued) (V+ = +15V, V- = -15V, VL = +5V, VIN_H = +2.4V, VIN_L = +0.8V, TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) (Note 2) μA μA μA TA = TMIN to TMAX 55 ns 400 Turn-On Time tON VCOM_ = ±10V, Figure 2 ns 300 Turn-Off Time tOFF VCOM_ = ±10V, Figure 2 CL = 1.0nF, VGEN = 0, RGEN = 0, Figure 4 Charge Injection Q 300 pC POWER SUPPLY SWITCH DYNAMIC CHARACTERISTICS TA = +25°C TA = TMIN to TMAX TA = +25°C TA = TMIN to TMAX MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches 4 _______________________________________________________________________________________ -5 5 -5 5 -20 20 TA = TMIN to TMAX V TA = +25°C GND V+ VCOM_, VNO_, VNC_ Input Voltage Range (Note 3) IN_ = 0.8V, all others = 2.4V +4.5 +36.0 IN_ = 2.4V, all others = 0.8V ICOM_ = 10mA, VNO_ or VNC_ = 10V TA = +25°C TA = +25°C TA = +25°C CONDITIONS Power-Supply Range VIN = 0 or 5V VIN = 0 or 5V -0.5 0.001 0.5 Logic Supply Current IL μA -0.5 0.001 0.5 Positive Supply Current I+ V μA VIN = 0 or 5V -0.5 0.001 0.5 Ground Current IGND μA TA = +25°C TA = +25°C TA = TMIN to TMAX TA = +25°C TA = TMIN to TMAX TA = TMIN to TMAX -5 5 -5 5 -5 5 Logic Input Voltage Low VIN_L 0.8 ½ 0.03 0.4 ÆRON COM_ to NO_ or NC_ On-Resistance Match Between Channels (Notes 3, 4) ½ 3 4 RON COM_ to NO or NC_ On-Resistance Logic Input Voltage High VIN_H 2.4 V IIN_L -0.5 0.001 0.5 Input Current with Input Voltage Low IIN_H -0.5 0.001 0.5 μA Input Current with Input Voltage High ½ 0.1 0.7 RFLAT(ON) COM_ to NO_ or NC_ On-Resistance Flatness (Notes 3, 5) nA I -0.5 0.01 0.5 NO_ INC_ Off-Leakage Current (NO_ or NC_) (Notes 6, 9) nA -0.5 0.01 0.5 ICOM_(OFF) COM Off-Leakage Current (Notes 6, 9) nA -1 0.01 1 ICOM_(ON) COM On-Leakage Current (Notes 6, 9) PARAMETER SYMBOL MIN TYP MAX UNITS ICOM_ = 10mA, VNO_ or = VNC_= 10V ICOM_ = 10mA; VNO_ or VNC_ = 3V, 6V, 9V TA = +25°C VCOM_ = 1V, 10V; VNO_ or VNC_ = 10V, 1V VNO_ or VNC_ = 10V, 1V; VCOM_ = 1V, 10V VCOM_ = 1V ,10V; VNO_ or VNC_ = 1V, 10V, or floating TA = +25°C TA = TMIN to TMAX 5 0.5 TA = TMIN to TMAX 0.8 TA = TMIN to TMAX TA = TMIN to TMAX TA = TMIN to TMAX ELECTRICAL CHARACTERISTICS—Single Supply (V+ = +12V, V- = 0, VL = +5V, VIN_H = +2.4V, VIN_L = +0.8V, TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) (Note 2) μA V ANALOG SWITCH LOGIC INPUT POWER SUPPLY MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches _______________________________________________________________________________________ 5 ELECTRICAL CHARACTERISTICS—Single Supply (continued) (V+ = +12V, V- = 0, VL = +5V, VIN_H = +2.4V, VIN_L = +0.8V, TA = TMIN to TMAX, unless otherwise noted. Typical values are at TA = +25°C.) (Note 2) Note 2: The algebraic convention, where the most negative value is a minimum and the most positive value a maximum, is used in this data sheet. Note 3: Guaranteed by design. Note 4: DRON = RON(MAX) - RON(MIN). Note 5: Flatness is defined as the difference between the maximum and minimum value of on-resistance as measured over the specified analog signal range. Note 6: Leakage parameters are 100% tested at maximum-rated hot temperature and guaranteed by correlation at +25°C. Note 7: Off-isolation = 20log10 [VCOM / (VNC or VNO)], VCOM = output, VNC or VNO = input to off switch. Note 8: Between any two switches. Note 9: Leakage testing at single supply is guaranteed by testing with dual supplies. 200 400 On-Capacitance CCOM f = 1MHz, Figure 8 140 pF COM Off-Capacitance CCOM f = 1MHz, Figure 7 85 pF NC_ or NO_ Capacitance COFF f = 1MHz, Figure 7 85 pF RL = 50½, CL = 5pF, f = 1MHz, Figure 6 VCOM_ = 10V, Figure 3, TA = +25°C PARAMETER SYMBOL MIN TYP MAX UNITS Crosstalk (Note 8) VCT -60 dB Break-Before-Make Time (MAX4663 only) (Note 3) tOPEN 5 125 ns 100 250 CONDITIONS Turn-On Time (Note 3) tON 500 ns VCOM_ = 10V, Figure 2 VCOM_ = 10V, Figure 2 Turn-Off Time (Note 3) tOFF 350 ns TA = +25°C TA = TMIN to TMAX TA = +25°C TA = TMIN to TMAX CL = 1.0nF, VGEN = 0, RGEN = 0, Figure 4 Charge Injection Q 20 pC SWITCH DYNAMIC CHARACTERISTICS MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches 6 _______________________________________________________________________________________ Typical Operating Characteristics (TA = +25°C, unless otherwise noted.) 0 1.0 0.5 2.0 1.5 3.0 2.5 3.5 4.5 4.0 5.0 -20 -15 -10 -5 0 5 10 15 20 ON-RESISTANCE vs. VCOM (DUAL SUPPLIES) MAX4661/2/3-01 VCOM (V) RON (W) V+, V- = ±5V V+, V- = ±15V V+, V- = ±20V 0 0.50 0.25 1.00 0.75 1.50 1.25 1.75 2.25 2.00 2.50 -15 -10 -5 0 5 10 15 ON-RESISTANCE vs. VCOM AND TEMPERATURE (DUAL SUPPLIES) MAX4661/2/3-02 VCOM (V) RON (W) TA = +85°C TA = +25°C V+, V- = ±15V TA = -40°C 0 3 2 1 4 5 6 7 8 9 10 0 2 4 6 8 10 12 14 16 18 20 22 24 ON-RESISTANCE vs. VCOM (SINGLE SUPPLY) MAX4661/2/3-03 VCOM (V) RON (W) V+ = 5V V+ = 12V V+ = 24V 0 1.50 1.00 0.50 2.00 2.50 3.50 3.00 4.00 0 1 2 3 4 5 6 7 8 9 10 11 12 ON-RESISTANCE vs. VCOM AND TEMPERATURE (SINGLE SUPPLY) MAX4661/2/3-04 VCOM (V) RON (W) TA = +85°C TA = +25°C TA = -40°C V+ = +12V V- = GND 0.1m 0.01 1m 10 1 0.1 1k 10k 100 100k -40 -20 0 20 40 60 80 100 ON/OFF-LEAKAGE CURRENT vs. TEMPERATURE MAX4661/2/3-05 TEMPERATURE (°C) LEAKAGE (pA) ON-LEAKAGE OFF-LEAKAGE V+ = +15V V- = -15V -200 0 -100 200 100 300 400 -20 -15 -10 -5 0 5 10 15 20 CHARGE INJECTION vs. VCOM MAX4661/2/3-06 VCOM (V) Q (pC) V- = -15V V+ = +15V V- = GND V+ = 12V 0.1 I+ I- 0.01 0.001 100 10 1 10k 100k 1k -40 -20 0 20 40 60 80 100 POWER-SUPPLY CURRENT vs. TEMPERATURE MAX4661/2/3-07 TEMPERATURE (°C) I+, I- (nA) V+ = +15V V- = -15V -10 -100 0.1 1 10 100 FREQUENCY RESPONSE -70 -90 -30 -50 0 -60 -80 -20 -40 MAX4661/2/3-08 FREQUENCY (MHz) LOSS (dB) 90 180 -720 -450 -630 -90 -270 -360 -540 -0 -180 PHASE (degrees) OFF-ISOLATION ON-PHASE ON-RESPONSE V+ = +15V V- = -15V INPUT = OdBm 50W IN AND OUT MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches _______________________________________________________________________________________ 7 NAME FUNCTION MAX4661 1, 16, 9, 8 IN1, IN2, IN3, IN4 Logic-Control Digital Inputs 2, 15, 10, 7 COM1, COM2, COM3, COM4 Analog Switch Common Terminals 3, 14, 11, 6 NC1, NC2, NC3, NC4 Analog Switch Normally Closed Terminals 4 VNegative Analog Supply-Voltage Input. Connect to GND for singlesupply operation. — NC2, NC3 Analog Switch Normally Closed Terminals — NO1, NO4 Analog Switch Normally Open Terminals — NO1, NO2, NO3, NO4 Analog Switch Normally Open Terminals 13 V+ Positive Analog Supply Input 12 VL Logic-Supply Input 5 GND Ground Pin Description MAX4662 1, 16, 9, 8 2, 15, 10, 7 — 4 — — 3, 14, 11, 6 13 12 5 MAX4663 1, 16, 9, 8 PIN 2, 15, 10, 7 — 4 14, 11 3, 6 — 13 12 5 Applications Information Overvoltage Protection Proper power-supply sequencing is recommended for all CMOS devices. Do not exceed the absolute maximum ratings because stresses beyond the listed ratings can cause permanent damage to the devices. Always sequence V+ on first, then V-, followed by the logic inputs, NO, or COM. If power-supply sequencing is not possible, add two small-signal diodes (D1, D2) in series with the supply pins and a Schottky diode between V+ and VL for overvoltage protection (Figure 1). Adding diodes reduces the analog signal range to one diode drop below V+ and one diode drop above V-, but does not affect the devices’ low switch resistance and low leakage characteristics. Device operation is unchanged, and the difference between V+ and Vshould not exceed 44V. Off-Isolation at High Frequencies In 50½ systems, the high-frequency on-response of these parts extends from DC to above 100MHz with a typical loss of -2dB. When the switch is turned off, however, it behaves like a capacitor and off-isolation decreases with increasing frequency. (Above 300MHz, the switch actually passes more signal turned off than turned on.) This effect is more pronounced with higher source and load impedances. Above 5MHz, circuit board layout becomes critical and it becomes difficult to characterize the response of the switch independent of the circuit. The graphs shown in the Typical Operating Characteristics were taken using a 50½ source and load connected with BNC connectors to a circuit board deemed “average”; that is, designed with isolation in mind, but not using stripline or other special RF circuit techniques. For critical applications above 5MHz, use the MAX440, MAX441, and MAX442, which are fully characterized up to 160MHz. COM_ VV+ VL NO_ * INTERNAL PROTECTION DIODES D2 D1 -15V +15V MAX4661 MAX4662 MAX4663 * * * * Figure 1. Overvoltage Protection Using External Blocking Diodes MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches 8 _______________________________________________________________________________________ 50% 0.9 · V0UT1 +3V 0V 0V LOGIC INPUT SWITCH OUTPUT 2 (VOUT2) 0V 0.9 · VOUT2 tD tD LOGIC INPUT V- -15V RL2 GND CL INCLUDES FIXTURE AND STRAY CAPACITANCE. COM2 IN1, 2 COM1 VOUT2 V+ +15V CL2 VCOM1 RL1 VOUT1 CL1 RL = 100W CL = 35pF NO NC SWITCH OUTPUT 1 (VOUT1) MAX4663 VCOM2 Figure 3. Break-Before-Make Interval (MAX4663 only) tr < 20ns tf < 20ns 50% 0 LOGIC INPUT V- -15V RL 100W NO_ OR NC_ GND CL INCLUDES FIXTURE AND STRAY CAPACITANCE. VO = VCOM ( RL RL + RON) SWITCH INPUT IN_ +3V tOFF 0 COM_ SWITCH OUTPUT 0.9V0 0.9V0 tON VO SWITCH OUTPUT LOGIC INPUT LOGIC INPUT WAVEFORMS INVERTED FOR SWITCHES THAT HAVE THE OPPOSITE LOGIC SENSE. VL V+ CL 35pF +5V +15V VCOM_ VO 0 REPEAT TEST FOR EACH SWITCH. FOR LOAD CONDITIONS, SEE Electrical Characteristics. MAX4661 MAX4662 MAX4663 Figure 2. Switching-Time Test Circuit MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches _______________________________________________________________________________________ 9 VGEN GND NC OR NO CL VO -15V VV+ VO VIN OFF ON OFF DVO Q = (DVO)(CL) COM +5V VIN DEPENDS ON SWITCH CONFIGURATION; INPUT POLARITY DETERMINED BY SENSE OF SWITCH. OFF ON OFF VIN VIN = +3V +15V RGEN IN VL MAX4661 MAX4662 MAX4663 Figure 4. Charge-Injection Test Circuit IN 0 OR 3.0V SIGNAL GENERATOR 0dBm +15V VL ANALYZER NC OR NO RL GND COM -15V V- +5V COM V+ MAX4661 MAX4662 MAX4663 Figure 5. Off-Isolation Test Circuit SIGNAL GENERATOR 0dBm +15V ANALYZER N_2 RL GND COM1 V- -15V 3.0V IN1 N_1 VL 50W COM2 +5V IN2 0 OR 3.0V N.C. V+ MAX4661 MAX4662 MAX4663 Figure 6. Crosstalk Test Circuit MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches 10 ______________________________________________________________________________________ CAPACITANCE METER NC OR NO COM GND V- -15V IN 0 OR 3.0V +15V VL +5V f = 1MHz V+ MAX4661 MAX4662 MAX4663 Figure 7. Switch Off-Capacitance Test Circuit CAPACITANCE METER NC OR NO COM GND V- -15V IN 0 OR 3.0V +15V VL +5V f = 1MHz V+ MAX4661 MAX4662 MAX4663 Figure 8. Switch On-Capacitance Test Circuit Chip Information TRANSISTOR COUNT: 108 Ordering Information (continued) PART TEMP. RANGE PIN-PACKAGE MAX4662CAE 0°C to +70°C 16 SSOP MAX4662CWE 0°C to +70°C 16 Wide SO MAX4662CPE 0°C to +70°C 16 Plastic DIP MAX4662EAE -40°C to +85°C 16 SSOP MAX4662EWE -40°C to +85°C 16 Wide SO MAX4662EPE -40°C to +85°C 16 Plastic DIP MAX4663CAE 0°C to +70°C 16 SSOP MAX4663CWE 0°C to +70°C 16 Wide SO MAX4663CPE 0°C to +70°C 16 Plastic DIP MAX4663EAE -40°C to +85°C 16 SSOP MAX4663EWE -40°C to +85°C 16 Wide SO MAX4663EPE -40°C to +85°C 16 Plastic DIP MAX4661/MAX4662/MAX4663 2.5W, Quad, SPST, CMOS Analog Switches ______________________________________________________________________________________ 11 Package Information SSOP.EPS MAX4661/MAX4662/MAX46663 2.5W, Quad, SPST, CMOS Analog Switches Maxim cannot assume responsibility for use of any circuitry other than circuitry entirely embodied in a Maxim product. No circuit patent licenses are implied. Maxim reserves the right to change the circuitry and specifications without notice at any time. 12 ____________________Maxim Integrated Products, 120 San Gabriel Drive, Sunnyvale, CA 94086 408-737-7600 © 1999 Maxim Integrated Products Printed USA is a registered trademark of Maxim Integrated Products. Package Information (continued) SOICW.EPS Copyright © 2010 Future Technology Devices International Limited 1 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Future Technology Devices International Ltd. FT232R USB UART IC The FT232R is a USB to serial UART interface with the following advanced features: Single chip USB to asynchronous serial data transfer interface. Entire USB protocol handled on the chip. No USB specific firmware programming required. Fully integrated 1024 bit EEPROM storing device descriptors and CBUS I/O configuration. Fully integrated USB termination resistors. Fully integrated clock generation with no external crystal required plus optional clock output selection enabling a glue-less interface to external MCU or FPGA. Data transfer rates from 300 baud to 3 Mbaud (RS422, RS485, RS232 ) at TTL levels. 128 byte receive buffer and 256 byte transmit buffer utilising buffer smoothing technology to allow for high data throughput. FTDI‟s royalty-free Virtual Com Port (VCP) and Direct (D2XX) drivers eliminate the requirement for USB driver development in most cases. Unique USB FTDIChip-ID™ feature. Configurable CBUS I/O pins. Transmit and receive LED drive signals. UART interface support for 7 or 8 data bits, 1 or 2 stop bits and odd / even / mark / space / no parity FIFO receive and transmit buffers for high data throughput. Synchronous and asynchronous bit bang interface options with RD# and WR# strobes. Device supplied pre-programmed with unique USB serial number. Supports bus powered, self powered and high-power bus powered USB configurations. Integrated +3.3V level converter for USB I/O. Integrated level converter on UART and CBUS for interfacing to between +1.8V and +5V logic. True 5V/3.3V/2.8V/1.8V CMOS drive output and TTL input. Configurable I/O pin output drive strength. Integrated power-on-reset circuit. Fully integrated AVCC supply filtering - no external filtering required. UART signal inversion option. +3.3V (using external oscillator) to +5.25V (internal oscillator) Single Supply Operation. Low operating and USB suspend current. Low USB bandwidth consumption. UHCI/OHCI/EHCI host controller compatible. USB 2.0 Full Speed compatible. -40°C to 85°C extended operating temperature range. Available in compact Pb-free 28 Pin SSOP and QFN-32 packages (both RoHS compliant). Neither the whole nor any part of the information contained in, or the product described in this manual, may be adapted or reproduced in any material or electronic form without the prior written consent of the copyright holder. This product and its documentation are supplied on an as-is basis and no warranty as to their suitability for any particular purpose is either made or implied. Future Technology Devices International Ltd will not accept any claim for damages howsoever arising as a result of use or failure of this product. Your statutory rights are not affected. This product or any variant of it is not intended for use in any medical appliance, device or system in which the failure of the product might reasonably be expected to result in personal injury. This document provides preliminary information that may be subject to change without notice. No freedom to use patents or other intellectual property rights is implied by the publication of this document. Future Technology Devices International Ltd, Unit 1, 2 Seaward Place, Centurion Business Park, Glasgow G41 1HH United Kingdom. Scotland Registered Company Number: SC136640 Copyright © 2010 Future Technology Devices International Limited 2 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 1 Typical Applications USB to RS232/RS422/RS485 Converters Upgrading Legacy Peripherals to USB Cellular and Cordless Phone USB data transfer cables and interfaces Interfacing MCU/PLD/FPGA based designs to USB USB Audio and Low Bandwidth Video data transfer PDA to USB data transfer USB Smart Card Readers USB Instrumentation USB Industrial Control USB MP3 Player Interface USB FLASH Card Reader and Writers Set Top Box PC - USB interface USB Digital Camera Interface USB Hardware Modems USB Wireless Modems USB Bar Code Readers USB Software and Hardware Encryption Dongles 1.1 Driver Support Royalty free VIRTUAL COM PORT (VCP) DRIVERS for... Windows 98, 98SE, ME, 2000, Server 2003, XP and Server 2008 Windows 7 32,64-bit Windows XP and XP 64-bit Windows Vista and Vista 64-bit Windows XP Embedded Windows CE 4.2, 5.0 and 6.0 Mac OS 8/9, OS-X Linux 2.4 and greater Royalty free D2XX Direct Drivers (USB Drivers + DLL S/W Interface) Windows 98, 98SE, ME, 2000, Server 2003, XP and Server 2008 Windows 7 32,64-bit Windows XP and XP 64-bit Windows Vista and Vista 64-bit Windows XP Embedded Windows CE 4.2, 5.0 and 6.0 Linux 2.4 and greater The drivers listed above are all available to download for free from FTDI website (www.ftdichip.com). Various 3rd party drivers are also available for other operating systems - see FTDI website (www.ftdichip.com) for details. For driver installation, please refer to http://www.ftdichip.com/Documents/InstallGuides.htm 1.2 Part Numbers Part Number Package FT232RQ-xxxx 32 Pin QFN FT232RL-xxxx 28 Pin SSOP Note: Packing codes for xxxx is: - Reel: Taped and Reel, (SSOP is 2,000pcs per reel, QFN is 6,000pcs per reel). - Tube: Tube packing, 47pcs per tube (SSOP only) - Tray: Tray packing, 490pcs per tray (QFN only) For example: FT232RQ-Reel is 6,000pcs taped and reel packing Copyright © 2010 Future Technology Devices International Limited 3 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 1.3 USB Compliant The FT232R is fully compliant with the USB 2.0 specification and has been given the USB-IF Test-ID (TID) 40680004 (Rev B) and 40770018 (Rev C). Copyright © 2010 Future Technology Devices International Limited 4 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 2 FT232R Block Diagram Figure 2.1 FT232R Block Diagram For a description of each function please refer to Section 4. x4 ClockMultiplierUARTFIFO ControllerSerial InterfaceEngine( SIE )USBProtocol EngineBaud RateGeneratorUART ControllerwithProgrammableSignal Inversion3.3 VoltLDORegulatorUSBTransceiverwithIntegratedSeriesResistorsand 1.5KPull-upUSB DPLLInternal12MHzOscillator48MHz48MHzOCSI(optional)OSCO(optional)USBDPUSBDM3V3OUTVCCDBUS0DBUS1DBUS2DBUS3DBUS4DBUS5DBUS6DBUS7CBUS0CBUS2CBUS3SLEEP#RESET#TESTGNDResetGenerator3V3OUTCBUS1FIFO RX BufferFIFO TX BufferInternalEEPROMTo USB Transeiver CellCBUS4 Copyright © 2010 Future Technology Devices International Limited 5 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Table of Contents 1 Typical Applications ........................................................................ 2 1.1 Driver Support .................................................................................... 2 1.2 Part Numbers...................................................................................... 2 Note: Packing codes for xxxx is: .................................................................. 2 1.3 USB Compliant .................................................................................... 3 2 FT232R Block Diagram .................................................................... 4 3 Device Pin Out and Signal Description ............................................ 7 3.1 28-LD SSOP Package .......................................................................... 7 3.2 SSOP Package Pin Out Description ...................................................... 7 3.3 QFN-32 Package ............................................................................... 10 3.4 QFN-32 Package Signal Description .................................................. 10 3.5 CBUS Signal Options ......................................................................... 13 4 Function Description ..................................................................... 14 4.1 Key Features ..................................................................................... 14 4.2 Functional Block Descriptions ........................................................... 15 5 Devices Characteristics and Ratings .............................................. 17 5.1 Absolute Maximum Ratings............................................................... 17 5.2 DC Characteristics............................................................................. 18 5.3 EEPROM Reliability Characteristics ................................................... 21 5.4 Internal Clock Characteristics ........................................................... 21 6 USB Power Configurations ............................................................ 23 6.1 USB Bus Powered Configuration ...................................................... 23 6.2 Self Powered Configuration .............................................................. 24 6.3 USB Bus Powered with Power Switching Configuration .................... 25 6.4 USB Bus Powered with Selectable External Logic Supply .................. 26 7 Application Examples .................................................................... 27 7.1 USB to RS232 Converter ................................................................... 27 7.2 USB to RS485 Coverter ..................................................................... 28 7.3 USB to RS422 Converter ................................................................... 29 7.4 USB to MCU UART Interface .............................................................. 30 7.5 LED Interface .................................................................................... 31 7.6 Using the External Oscillator ............................................................ 32 8 Internal EEPROM Configuration .................................................... 33 9 Package Parameters ..................................................................... 35 9.1 SSOP-28 Package Dimensions .......................................................... 35 Copyright © 2010 Future Technology Devices International Limited 6 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 9.2 QFN-32 Package Dimensions ............................................................ 36 9.3 QFN-32 Package Typical Pad Layout ................................................. 37 9.4 QFN-32 Package Typical Solder Paste Diagram ................................. 37 9.5 Solder Reflow Profile ........................................................................ 38 10 Contact Information ................................................................... 39 Appendix A – References ........................................................................... 40 Appendix B - List of Figures and Tables ..................................................... 41 Appendix C - Revision History .................................................................... 43 Copyright © 2010 Future Technology Devices International Limited 7 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 3 Device Pin Out and Signal Description 3.1 28-LD SSOP Package Figure 3.1 SSOP Package Pin Out and Schematic Symbol 3.2 SSOP Package Pin Out Description Note: The convention used throughout this document for active low signals is the signal name followed by a # Pin No. Name Type Description 15 USBDP I/O USB Data Signal Plus, incorporating internal series resistor and 1.5kΩ pull up resistor to 3.3V. 16 USBDM I/O USB Data Signal Minus, incorporating internal series resistor. Table 3.1 USB Interface Group Pin No. Name Type Description 4 VCCIO PWR +1.8V to +5.25V supply to the UART Interface and CBUS group pins (1...3, 5, 6, 9...14, 22, 23). In USB bus powered designs connect this pin to 3V3OUT pin to drive out at +3.3V levels, or connect to VCC to drive out at 5V CMOS level. This pin can also be supplied with an external +1.8V to +2.8V supply in order to drive outputs at lower levels. It should be noted that in this case this supply should originate from the same source as the supply to VCC. This means that in bus powered designs a regulator which is supplied by the +5V on the USB bus should be used. 7, 18, 21 GND PWR Device ground supply pins USBDPUSBDM3V3OUTGNDRESET#VCCGNDNCAGNDTESTOSCIOSCOCBUS1CBUS0TXDRTS#RXDDTR#VCCIORI#GNDNCDSR#DCD#CTS#CBUS4CBUS2CBUS31141528FT232RLAGNDGNDGNDGNDTEST2571821263V3OUTVCCIO417NCRESET#NC24198TXDRXDRTS#CTS#DTR#DSR#DCD#RI#1531129106CBUS0CBUS3CBUS2CBUS123221314201615USBDPUSBDMVCCOSCI27OSCO28CBUS412FTDIFT232RLYYXX-AXXXXXXXXXXXX Copyright © 2010 Future Technology Devices International Limited 8 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Pin No. Name Type Description 17 3V3OUT Output +3.3V output from integrated LDO regulator. This pin should be decoupled to ground using a 100nF capacitor. The main use of this pin is to provide the internal +3.3V supply to the USB transceiver cell and the internal 1.5kΩ pull up resistor on USBDP. Up to 50mA can be drawn from this pin to power external logic if required. This pin can also be used to supply the VCCIO pin. 20 VCC PWR +3.3V to +5.25V supply to the device core. (see Note 1) 25 AGND PWR Device analogue ground supply for internal clock multiplier Table 3.2 Power and Ground Group Pin No. Name Type Description 8, 24 NC NC No internal connection 19 RESET# Input Active low reset pin. This can be used by an external device to reset the FT232R. If not required can be left unconnected, or pulled up to VCC. 26 TEST Input Puts the device into IC test mode. Must be tied to GND for normal operation, otherwise the device will appear to fail. 27 OSCI Input Input 12MHz Oscillator Cell. Optional – Can be left unconnected for normal operation. (see Note 2) 28 OSCO Output Output from 12MHZ Oscillator Cell. Optional – Can be left unconnected for normal operation if internal Oscillator is used. (see Note 2) Table 3.3 Miscellaneous Signal Group Pin No. Name Type Description 1 TXD Output Transmit Asynchronous Data Output. 2 DTR# Output Data Terminal Ready Control Output / Handshake Signal. 3 RTS# Output Request to Send Control Output / Handshake Signal. 5 RXD Input Receiving Asynchronous Data Input. 6 RI# Input Ring Indicator Control Input. When remote wake up is enabled in the internal EEPROM taking RI# low (20ms active low pulse) can be used to resume the PC USB host controller from suspend. 9 DSR# Input Data Set Ready Control Input / Handshake Signal. 10 DCD# Input Data Carrier Detect Control Input. 11 CTS# Input Clear To Send Control Input / Handshake Signal. 12 CBUS4 I/O Configurable CBUS output only Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is SLEEP#. See CBUS Signal Options, Table 3.9. 13 CBUS2 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is TXDEN. See CBUS Signal Options, Table 3.9. Copyright © 2010 Future Technology Devices International Limited 9 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Pin No. Name Type Description 14 CBUS3 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is PWREN#. See CBUS Signal Options, Table 3.9. PWREN# should be used with a 10kΩ resistor pull up. 22 CBUS1 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is RXLED#. See CBUS Signal Options, Table 3.9. 23 CBUS0 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is TXLED#. See CBUS Signal Options, Table 3.9. Table 3.4 UART Interface and CUSB Group (see note 3) Notes: 1. The minimum operating voltage VCC must be +4.0V (could use VBUS=+5V) when using the internal clock generator. Operation at +3.3V is possible using an external crystal oscillator. 2. For details on how to use an external crystal, ceramic resonator, or oscillator with the FT232R, please refer Section 7.6 3. When used in Input Mode, the input pins are pulled to VCCIO via internal 200kΩ resistors. These pins can be programmed to gently pull low during USB suspend (PWREN# = “1”) by setting an option in the internal EEPROM. Copyright © 2010 Future Technology Devices International Limited 10 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 3.3 QFN-32 Package Figure 3.2 QFN-32 Package Pin Out and schematic symbol 3.4 QFN-32 Package Signal Description Pin No. Name Type Description 14 USBDP I/O USB Data Signal Plus, incorporating internal series resistor and 1.5kΩ pull up resistor to +3.3V. 15 USBDM I/O USB Data Signal Minus, incorporating internal series resistor. Table 3.5 USB Interface Group Pin No. Name Type Description 1 VCCIO PWR +1.8V to +5.25V supply for the UART Interface and CBUS group pins (2, 3, 6,7,8,9,10 11, 21, 22, 30,31,32). In USB bus powered designs connect this pin to 3V3OUT to drive out at +3.3V levels, or connect to VCC to drive out at +5V CMOS level. This pin can also be supplied with an external +1.8V to +2.8V supply in order to drive out at lower levels. It should be noted that in this case this supply should originate from the same source as the supply to VCC. This means that in bus powered designs a regulator which is supplied by the +5V on the USB bus should be used. 4, 17, 20 GND PWR Device ground supply pins. FT232RQ3225241716981YYXX-A1891234567810111213141516171920212223242526272829303132USBDPUSBDM3V3OUTRESET#VCCNCAGNDTESTOSCIOSCOCBUS1CBUS0TXDRTS#RXDDTR#VCCIORI#GNDNCDSR#DCD#CTS#CBUS4CBUS2CBUS3GNDGNDNCNCNCNCIFT232RQAGNDGNDGNDGNDTEST2441720263V3OUTVCCIO116NCRESET#NC231813TXDRXDRTS#CTS#DTR#DSR#DCD#RI#30232831673CBUS0CBUS3CBUS2CBUS122211011191514USBDPUSBDMVCCOSCI27OSCO28CBUS49NC12NC5NC29NC25FTDXXXXXXX Copyright © 2010 Future Technology Devices International Limited 11 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Pin No. Name Type Description 16 3V3OUT Output +3.3V output from integrated LDO regulator. This pin should be decoupled to ground using a 100nF capacitor. The purpose of this output is to provide the internal +3.3V supply to the USB transceiver cell and the internal 1.5kΩ pull up resistor on USBDP. Up to 50mA can be drawn from this pin to power external logic if required. This pin can also be used to supply the VCCIO pin. 19 VCC PWR +3.3V to +5.25V supply to the device core. (See Note 1). 24 AGND PWR Device analogue ground supply for internal clock multiplier. Table 3.6 Power and Ground Group Pin No. Name Type Description 5, 12, 13, 23, 25, 29 NC NC No internal connection. Do not connect. 18 RESET# Input Active low reset. Can be used by an external device to reset the FT232R. If not required can be left unconnected, or pulled up to VCC. 26 TEST Input Puts the device into IC test mode. Must be tied to GND for normal operation, otherwise the device will appear to fail. 27 OSCI Input Input 12MHz Oscillator Cell. Optional – Can be left unconnected for normal operation. (See Note 2). 28 OSCO Output Output from 12MHZ Oscillator Cell. Optional – Can be left unconnected for normal operation if internal Oscillator is used. (See Note 2). Table 3.7 Miscellaneous Signal Group Pin No. Name Type Description 30 TXD Output Transmit Asynchronous Data Output. 31 DTR# Output Data Terminal Ready Control Output / Handshake Signal. 32 RTS# Output Request to Send Control Output / Handshake Signal. 2 RXD Input Receiving Asynchronous Data Input. 3 RI# Input Ring Indicator Control Input. When remote wake up is enabled in the internal EEPROM taking RI# low (20ms active low pulse) can be used to resume the PC USB host controller from suspend. 6 DSR# Input Data Set Ready Control Input / Handshake Signal. 7 DCD# Input Data Carrier Detect Control Input. 8 CTS# Input Clear To Send Control Input / Handshake Signal. 9 CBUS4 I/O Configurable CBUS output only Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is SLEEP#. See CBUS Signal Options, Table 3.9. 10 CBUS2 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is TXDEN. See CBUS Signal Options, Table 3.9. Copyright © 2010 Future Technology Devices International Limited 12 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Pin No. Name Type Description 11 CBUS3 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is PWREN#. See CBUS Signal Options, Table 3.9. PWREN# should be used with a 10kΩ resistor pull up. 21 CBUS1 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is RXLED#. See CBUS Signal Options, Table 3.9. 22 CBUS0 I/O Configurable CBUS I/O Pin. Function of this pin is configured in the device internal EEPROM. Factory default configuration is TXLED#. See CBUS Signal Options, Table 3.9. Table 3.8 UART Interface and CBUS Group (see note 3) Notes: 1. The minimum operating voltage VCC must be +4.0V (could use VBUS=+5V) when using the internal clock generator. Operation at +3.3V is possible using an external crystal oscillator. 2. For details on how to use an external crystal, ceramic resonator, or oscillator with the FT232R, please refer to Section 7.6. 3. When used in Input Mode, the input pins are pulled to VCCIO via internal 200kΩ resistors. These pins can be programmed to gently pull low during USB suspend (PWREN# = “1”) by setting an option in the internal EEPROM. Copyright © 2010 Future Technology Devices International Limited 13 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 3.5 CBUS Signal Options The following options can be configured on the CBUS I/O pins. CBUS signal options are common to both package versions of the FT232R. These options can be configured in the internal EEPROM using the software utility FT_PPROG or MPROG, which can be downloaded from the FTDI Utilities (www.ftdichip.com). The default configuration is described in Section 8. CBUS Signal Option Available On CBUS Pin Description TXDEN CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 Enable transmit data for RS485 PWREN# CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 Output is low after the device has been configured by USB, then high during USB suspend mode. This output can be used to control power to external logic P-Channel logic level MOSFET switch. Enable the interface pull-down option when using the PWREN# in this way.* TXLED# CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 Transmit data LED drive: Data from USB Host to FT232R. Pulses low when transmitting data via USB. See Section 7.5 for more details. RXLED# CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 Receive data LED drive: Data from FT232R to USB Host. Pulses low when receiving data via USB. See Section 7.5 for more details. TX&RXLED# CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 LED drive – pulses low when transmitting or receiving data via USB. See Section 7.5 for more details. SLEEP# CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 Goes low during USB suspend mode. Typically used to power down an external TTL to RS232 level converter IC in USB to RS232 converter designs. CLK48 CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 48MHz ±0.7% Clock output. ** CLK24 CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 24 MHz Clock output.** CLK12 CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 12 MHz Clock output.** CLK6 CBUS0, CBUS1, CBUS2, CBUS3, CBUS4 6 MHz ±0.7% Clock output. ** CBitBangI/O CBUS0, CBUS1, CBUS2, CBUS3 CBUS bit bang mode option. Allows up to 4 of the CBUS pins to be used as general purpose I/O. Configured individually for CBUS0, CBUS1, CBUS2 and CBUS3 in the internal EEPROM. A separate application note, AN232R-01, available from FTDI website (www.ftdichip.com) describes in more detail how to use CBUS bit bang mode. BitBangWRn CBUS0, CBUS1, CBUS2, CBUS3 Synchronous and asynchronous bit bang mode WR# strobe output. BitBangRDn CBUS0, CBUS1, CBUS2, CBUS3 Synchronous and asynchronous bit bang mode RD# strobe output. Table 3.9 CBUS Configuration Control * PWREN# must be used with a 10kΩ resistor pull up. **When in USB suspend mode the outputs clocks are also suspended. Copyright © 2010 Future Technology Devices International Limited 14 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 4 Function Description The FT232R is a USB to serial UART interface device which simplifies USB to serial designs and reduces external component count by fully integrating an external EEPROM, USB termination resistors and an integrated clock circuit which requires no external crystal, into the device. It has been designed to operate efficiently with a USB host controller by using as little as possible of the total USB bandwidth available. 4.1 Key Features Functional Integration. Fully integrated EEPROM, USB termination resistors, clock generation, AVCC filtering, POR and LDO regulator. Configurable CBUS I/O Pin Options. The fully integrated EEPROM allows configuration of the Control Bus (CBUS) functionality, signal inversion and drive strength selection. There are 5 configurable CBUS I/O pins. These configurable options are 1. TXDEN - transmit enable for RS485 designs. 2. PWREN# - Power control for high power, bus powered designs. 3. TXLED# - for pulsing an LED upon transmission of data. 4. RXLED# - for pulsing an LED upon receiving data. 5. TX&RXLED# - which will pulse an LED upon transmission OR reception of data. 6. SLEEP# - indicates that the device going into USB suspend mode. 7. CLK48 / CLK24 / CLK12 / CLK6 - 48MHz, 24MHz, 12MHz, and 6MHz clock output signal options. The CBUS pins can also be individually configured as GPIO pins, similar to asynchronous bit bang mode. It is possible to use this mode while the UART interface is being used, thus providing up to 4 general purpose I/O pins which are available during normal operation. An application note, AN232R-01, available from FTDI website (www.ftdichip.com) describes this feature. The CBUS lines can be configured with any one of these output options by setting bits in the internal EEPROM. The device is supplied with the most commonly used pin definitions pre-programmed - see Section 8 for details. Asynchronous Bit Bang Mode with RD# and WR# Strobes. The FT232R supports FTDI‟s previous chip generation bit-bang mode. In bit-bang mode, the eight UART lines can be switched from the regular interface mode to an 8-bit general purpose I/O port. Data packets can be sent to the device and they will be sequentially sent to the interface at a rate controlled by an internal timer (equivalent to the baud rate pre-scaler). With the FT232R device this mode has been enhanced by outputting the internal RD# and WR# strobes signals which can be used to allow external logic to be clocked by accesses to the bit-bang I/O bus. This option will be described more fully in a separate application note available from FTDI website (www.ftdichip.com). Synchronous Bit Bang Mode. The FT232R supports synchronous bit bang mode. This mode differs from asynchronous bit bang mode in that the interface pins are only read when the device is written to. This makes it easier for the controlling program to measure the response to an output stimulus as the data returned is synchronous to the output data. An application note, AN232R-01, available from FTDI website (www.ftdichip.com) describes this feature. FTDIChip-ID™. The FT232R also includes the new FTDIChip-ID™ security dongle feature. This FTDIChip-ID™ feature allows a unique number to be burnt into each device during manufacture. This number cannot be reprogrammed. This number is only readable over USB and forms a basis of a security dongle which can be used to protect any customer application software being copied. This allows the possibility of using the FT232R in a dongle for software licensing. Further to this, a renewable license scheme can be implemented based on the FTDIChip-ID™ number when encrypted with other information. This encrypted number can be stored in the user area of the FT232R internal EEPROM, and can be decrypted, then compared with the protected FTDIChip-ID™ to verify that a license is valid. Web based applications can be used to maintain product licensing this way. An application note, AN232R-02, available from FTDI website (www.ftdichip.com) describes this feature. The FT232R is capable of operating at a voltage supply between +3.3V and +5V with a nominal operational mode current of 15mA and a nominal USB suspend mode current of 70μA. This allows greater margin for peripheral designs to meet the USB suspend mode current limit of 2.5mA. An integrated level converter within the UART interface allows the FT232R to interface to UART logic running at +1.8V, 2.5V, +3.3V or +5V. Copyright © 2010 Future Technology Devices International Limited 15 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 4.2 Functional Block Descriptions The following paragraphs detail each function within the FT232R. Please refer to the block diagram shown in Figure 2.1 Internal EEPROM. The internal EEPROM in the FT232R is used to store USB Vendor ID (VID), Product ID (PID), device serial number, product description string and various other USB configuration descriptors. The internal EEPROM is also used to configure the CBUS pin functions. The FT232R is supplied with the internal EEPROM pre-programmed as described in Section 8. A user area of the internal EEPROM is available to system designers to allow storing additional data. The internal EEPROM descriptors can be programmed in circuit, over USB without any additional voltage requirement. It can be programmed using the FTDI utility software called MPROG, which can be downloaded from FTDI Utilities on the FTDI website (www.ftdichip.com). +3.3V LDO Regulator. The +3.3V LDO regulator generates the +3.3V reference voltage for driving the USB transceiver cell output buffers. It requires an external decoupling capacitor to be attached to the 3V3OUT regulator output pin. It also provides +3.3V power to the 1.5kΩ internal pull up resistor on USBDP. The main function of the LDO is to power the USB Transceiver and the Reset Generator Cells rather than to power external logic. However, it can be used to supply external circuitry requiring a +3.3V nominal supply with a maximum current of 50mA. USB Transceiver. The USB Transceiver Cell provides the USB 1.1 / USB 2.0 full-speed physical interface to the USB cable. The output drivers provide +3.3V level slew rate control signalling, whilst a differential input receiver and two single ended input receivers provide USB data in, Single-Ended-0 (SE0) and USB reset detection conditions respectfully. This function also incorporates the internal USB series termination resistors on the USB data lines and a 1.5kΩ pull up resistor on USBDP. USB DPLL. The USB DPLL cell locks on to the incoming NRZI USB data and generates recovered clock and data signals for the Serial Interface Engine (SIE) block. Internal 12MHz Oscillator - The Internal 12MHz Oscillator cell generates a 12MHz reference clock. This provides an input to the x4 Clock Multiplier function. The 12MHz Oscillator is also used as the reference clock for the SIE, USB Protocol Engine and UART FIFO controller blocks. Clock Multiplier / Divider. The Clock Multiplier / Divider takes the 12MHz input from the Internal Oscillator function and generates the 48MHz, 24MHz, 12MHz and 6MHz reference clock signals. The 48Mz clock reference is used by the USB DPLL and the Baud Rate Generator blocks. Serial Interface Engine (SIE). The Serial Interface Engine (SIE) block performs the parallel to serial and serial to parallel conversion of the USB data. In accordance with the USB 2.0 specification, it performs bit stuffing/un-stuffing and CRC5/CRC16 generation. It also checks the CRC on the USB data stream. USB Protocol Engine. The USB Protocol Engine manages the data stream from the device USB control endpoint. It handles the low level USB protocol requests generated by the USB host controller and the commands for controlling the functional parameters of the UART in accordance with the USB 2.0 specification chapter 9. FIFO RX Buffer (128 bytes). Data sent from the USB host controller to the UART via the USB data OUT endpoint is stored in the FIFO RX (receive) buffer. Data is removed from the buffer to the UART transmit register under control of the UART FIFO controller. (Rx relative to the USB interface). FIFO TX Buffer (256 bytes). Data from the UART receive register is stored in the TX buffer. The USB host controller removes data from the FIFO TX Buffer by sending a USB request for data from the device data IN endpoint. (Tx relative to the USB interface). UART FIFO Controller. The UART FIFO controller handles the transfer of data between the FIFO RX and TX buffers and the UART transmit and receive registers. UART Controller with Programmable Signal Inversion and High Drive. Together with the UART FIFO Controller the UART Controller handles the transfer of data between the FIFO RX and FIFO TX buffers and the UART transmit and receive registers. It performs asynchronous 7 or 8 bit parallel to serial and serial to parallel conversion of the data on the RS232 (or RS422 or RS485) interface. Control signals supported by UART mode include RTS, CTS, DSR, DTR, DCD and RI. The UART Controller also provides a transmitter enable control signal pin option (TXDEN) to assist with interfacing to RS485 transceivers. RTS/CTS, DSR/DTR and XON / XOFF handshaking options are also supported. Handshaking is handled in hardware to ensure fast response times. The UART interface also supports the RS232 BREAK setting and detection conditions. Copyright © 2010 Future Technology Devices International Limited 16 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Additionally, the UART signals can each be individually inverted and have a configurable high drive strength capability. Both these features are configurable in the EEPROM. Baud Rate Generator - The Baud Rate Generator provides a 16x clock input to the UART Controller from the 48MHz reference clock. It consists of a 14 bit pre-scaler and 3 register bits which provide fine tuning of the baud rate (used to divide by a number plus a fraction or “sub-integer”). This determines the baud rate of the UART, which is programmable from 183 baud to 3 Mbaud. The FT232R supports all standard baud rates and non-standard baud rates from 183 Baud up to 3 Mbaud. Achievable non-standard baud rates are calculated as follows - Baud Rate = 3000000 / (n + x) where „n‟ can be any integer between 2 and 16,384 ( = 214 ) and „x’ can be a sub-integer of the value 0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, or 0.875. When n = 1, x = 0, i.e. baud rate divisors with values between 1 and 2 are not possible. This gives achievable baud rates in the range 183.1 baud to 3,000,000 baud. When a non-standard baud rate is required simply pass the required baud rate value to the driver as normal, and the FTDI driver will calculate the required divisor, and set the baud rate. See FTDI application note AN232B-05 on the FTDI website (www.ftdichip.com) for more details. RESET Generator - The integrated Reset Generator Cell provides a reliable power-on reset to the device internal circuitry at power up. The RESET# input pin allows an external device to reset the FT232R. RESET# can be tied to VCC or left unconnected if not being used. Copyright © 2010 Future Technology Devices International Limited 17 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 5 Devices Characteristics and Ratings 5.1 Absolute Maximum Ratings The absolute maximum ratings for the FT232R devices are as follows. These are in accordance with the Absolute Maximum Rating System (IEC 60134). Exceeding these may cause permanent damage to the device. Parameter Value Unit Storage Temperature -65°C to 150°C Degrees C Floor Life (Out of Bag) At Factory Ambient (30°C / 60% Relative Humidity) 168 Hours (IPC/JEDEC J-STD-033A MSL Level 3 Compliant)* Hours Ambient Temperature (Power Applied) -40°C to 85°C Degrees C MTTF FT232RL 11162037 hours MTTF FT232RQ 4464815 hours VCC Supply Voltage -0.5 to +6.00 V DC Input Voltage – USBDP and USBDM -0.5 to +3.8 V DC Input Voltage – High Impedance Bidirectionals -0.5 to + (VCC +0.5) V DC Input Voltage – All Other Inputs -0.5 to + (VCC +0.5) V DC Output Current – Outputs 24 mA DC Output Current – Low Impedance Bidirectionals 24 mA Power Dissipation (VCC = 5.25V) 500 mW Table 5.1 Absolute Maximum Ratings * If devices are stored out of the packaging beyond this time limit the devices should be baked before use. The devices should be ramped up to a temperature of +125°C and baked for up to 17 hours. Copyright © 2010 Future Technology Devices International Limited 18 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 5.2 DC Characteristics DC Characteristics (Ambient Temperature = -40°C to +85°C) Parameter Description Minimum Typical Maximum Units Conditions VCC1 VCC Operating Supply Voltage 4.0 --- 5.25 V Using Internal Oscillator VCC1 VCC Operating Supply Voltage 3.3 --- 5.25 V Using External Crystal VCC2 VCCIO Operating Supply Voltage 1.8 --- 5.25 V Icc1 Operating Supply Current --- 15 --- mA Normal Operation Icc2 Operating Supply Current 50 70 100 μA USB Suspend 3V3 3.3v regulator output 3.0 3.3 3.6 V Table 5.2 Operating Voltage and Current Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 3.2 4.1 4.9 V I source = 2mA Vol Output Voltage Low 0.3 0.4 0.6 V I sink = 2mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.3 UART and CBUS I/O Pin Characteristics (VCCIO = +5.0V, Standard Drive Level) Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 2.2 2.7 3.2 V I source = 1mA Vol Output Voltage Low 0.3 0.4 0.5 V I sink = 2mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.4 UART and CBUS I/O Pin Characteristics (VCCIO = +3.3V, Standard Drive Level) Copyright © 2010 Future Technology Devices International Limited 19 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 2.1 2.6 2.8 V I source = 1mA Vol Output Voltage Low 0.3 0.4 0.5 V I sink = 2mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.5 UART and CBUS I/O Pin Characteristics (VCCIO = +2.8V, Standard Drive Level) Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 1.32 1.62 1.8 V I source = 0.2mA Vol Output Voltage Low 0.06 0.1 0.18 V I sink = 0.5mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.6 UART and CBUS I/O Pin Characteristics (VCCIO = +1.8V, Standard Drive Level) Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 3.2 4.1 4.9 V I source = 6mA Vol Output Voltage Low 0.3 0.4 0.6 V I sink = 6mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.7 UART and CBUS I/O Pin Characteristics (VCCIO = +5.0V, High Drive Level) Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 2.2 2.8 3.2 V I source = 3mA Vol Output Voltage Low 0.3 0.4 0.6 V I sink = 8mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.8 UART and CBUS I/O Pin Characteristics (VCCIO = +3.3V, High Drive Level) Copyright © 2010 Future Technology Devices International Limited 20 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 2.1 2.6 2.8 V I source = 3mA Vol Output Voltage Low 0.3 0.4 0.6 V I sink = 8mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.9 UART and CBUS I/O Pin Characteristics (VCCIO = +2.8V, High Drive Level) Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 1.35 1.67 1.8 V I source = 0.4mA Vol Output Voltage Low 0.12 0.18 0.35 V I sink = 3mA Vin Input Switching Threshold 1.0 1.2 1.5 V ** VHys Input Switching Hysteresis 20 25 30 mV ** Table 5.10 UART and CBUS I/O Pin Characteristics (VCCIO = +1.8V, High Drive Level) ** Only input pins have an internal 200KΩ pull-up resistor to VCCIO Parameter Description Minimum Typical Maximum Units Conditions Vin Input Switching Threshold 1.3 1.6 1.9 V VHys Input Switching Hysteresis 50 55 60 mV Table 5.11 RESET# and TEST Pin Characteristics Copyright © 2010 Future Technology Devices International Limited 21 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Parameter Description Minimum Typical Maximum Units Conditions UVoh I/O Pins Static Output (High) 2.8 3.6 V RI = 1.5kΩ to 3V3OUT (D+) RI = 15KΩ to GND (D-) UVol I/O Pins Static Output (Low) 0 0.3 V RI = 1.5kΩ to 3V3OUT (D+) RI = 15kΩ to GND (D-) UVse Single Ended Rx Threshold 0.8 2.0 V UCom Differential Common Mode 0.8 2.5 V UVDif Differential Input Sensitivity 0.2 V UDrvZ Driver Output Impedance 26 29 44 Ohms See Note 1 Table 5.12 USB I/O Pin (USBDP, USBDM) Characteristics 5.3 EEPROM Reliability Characteristics The internal 1024 Bit EEPROM has the following reliability characteristics: Parameter Value Unit Data Retention 10 Years Read / Write Cycle 10,000 Cycles Table 5.13 EEPROM Characteristics 5.4 Internal Clock Characteristics The internal Clock Oscillator has the following characteristics: Parameter Value Unit Minimum Typical Maximum Frequency of Operation (see Note 1) 11.98 12.00 12.02 MHz Clock Period 83.19 83.33 83.47 ns Duty Cycle 45 50 55 % Table 5.14 Internal Clock Characteristics Note 1: Equivalent to +/-1667ppm Copyright © 2010 Future Technology Devices International Limited 22 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Parameter Description Minimum Typical Maximum Units Conditions Voh Output Voltage High 2.1 2.8 3.2 V I source = 3mA Vol Output Voltage Low 0.3 0.4 0.6 V I sink = 8mA Vin Input Switching Threshold 1.0 1.2 1.5 V Table 5.15 OSCI, OSCO Pin Characteristics – see Note 1 Note1: When supplied, the FT232R is configured to use its internal clock oscillator. These characteristics only apply when an external oscillator or crystal is used. Copyright © 2010 Future Technology Devices International Limited 23 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 6 USB Power Configurations The following sections illustrate possible USB power configurations for the FT232R. The illustrations have omitted pin numbers for ease of understanding since the pins differ between the FT232RL and FT232RQ package options. All USB power configurations illustrated apply to both package options for the FT232R device. Please refer to Section 3 for the package option pin-out and signal descriptions. 6.1 USB Bus Powered Configuration Figure 6.1 Bus Powered Configuration Figure 6.1 Illustrates the FT232R in a typical USB bus powered design configuration. A USB bus powered device gets its power from the USB bus. Basic rules for USB bus power devices are as follows – i) On plug-in to USB, the device should draw no more current than 100mA. ii) In USB Suspend mode the device should draw no more than 2.5mA. iii) A bus powered high power USB device (one that draws more than 100mA) should use one of the CBUS pins configured as PWREN# and use it to keep the current below 100mA on plug-in and 2.5mA on USB suspend. iv) A device that consumes more than 100mA cannot be plugged into a USB bus powered hub. v) No device can draw more than 500mA from the USB bus. The power descriptors in the internal EEPROM of the FT232R should be programmed to match the current drawn by the device. A ferrite bead is connected in series with the USB power supply to reduce EMI noise from the FT232R and associated circuitry being radiated down the USB cable to the USB host. The value of the Ferrite Bead depends on the total current drawn by the application. A suitable range of Ferrite Beads is available from Steward (www.steward.com), for example Steward Part # MI0805K400R-10. Note: If using PWREN# (available using the CBUS) the pin should be pulled to VCCIO using a 10kΩ resistor. FT232RAGNDGNDGNDGNDTEST100nF3V3OUTVCCIONCRESET#NC+100nF10nFVccTXDRXDRTS#CTS#DTR#DSR#DCD#RI#CBUS0CBUS3CBUS2CBUS1USBDPUSBDMVCC12345OSCIOSCOCBUS4FerriteBead+4.7uFSHIELDGNDGNDGNDGNDVcc Copyright © 2010 Future Technology Devices International Limited 24 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 6.2 Self Powered Configuration Figure 6.2 Self Powered Configuration Figure 6.2 illustrates the FT232R in a typical USB self powered configuration. A USB self powered device gets its power from its own power supply, VCC, and does not draw current from the USB bus. The basic rules for USB self powered devices are as follows – i) A self powered device should not force current down the USB bus when the USB host or hub controller is powered down. ii) A self powered device can use as much current as it needs during normal operation and USB suspend as it has its own power supply. iii) A self powered device can be used with any USB host, a bus powered USB hub or a self powered USB hub. The power descriptor in the internal EEPROM of the FT232R should be programmed to a value of zero (self powered). In order to comply with the first requirement above, the USB bus power (pin 1) is used to control the RESET# pin of the FT232R device. When the USB host or hub is powered up an internal 1.5kΩ resistor on USBDP is pulled up to +3.3V (generated using the 4K7 and 10k resistor network), thus identifying the device as a full speed device to the USB host or hub. When the USB host or hub is powered off, RESET# will be low and the FT232R is held in reset. Since RESET# is low, the internal 1.5kΩ resistor is not pulled up to any power supply (hub or host is powered down), so no current flows down USBDP via the 1.5kΩ pull-up resistor. Failure to do this may cause some USB host or hub controllers to power up erratically. Figure 6.2 illustrates a self powered design which has a +4V to +5.25V supply. Note: 1. When the FT232R is in reset, the UART interface I/O pins are tri-stated. Input pins have internal 200kΩ pull-up resistors to VCCIO, so they will gently pull high unless driven by some external logic. 2. When using internal FT232R oscillator the VCC supply voltage range must be +4.0V to 5.25V. 3. When using external oscillator the VCC supply voltage range must be +3.3V to 5.25V Any design which interfaces to +3.3 V or +1.8V would be having a +3.3V or +1.8V supply to VCCIO. Copyright © 2010 Future Technology Devices International Limited 25 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 6.3 USB Bus Powered with Power Switching Configuration FT232R GND GND 100nF VCC USBDM USBDP VCCIO NC RESET# NC OSCI OSCO 3V3OUT A G N D G N D G N D G N D T E S T TXD RXD RTS# CTS# DTR# DSR# DCD# RI# CBUS0 CBUS1 CBUS2 CBUS3 CBUS4 1 2 3 4 GND SHIELD GND 100nF 4.7uF + 5 10nF + Ferrite Bead s d g P-Channel Power MOSFET PWREN# 1K Switched 5V Power To External Logic Soft Start Circuit 0.1uF 0.1uF 5V VCC 5V VCC 5V VCC 10K Figure 6.3 Bus Powered with Power Switching Configuration A requirement of USB bus powered applications, is when in USB suspend mode, the application draws a total current of less than 2.5mA. This requirement includes external logic. Some external logic has the ability to power itself down into a low current state by monitoring the PWREN# signal. For external logic that cannot power itself down in this way, the FT232R provides a simple but effective method of turning off power during the USB suspend mode. Figure 6.3 shows an example of using a discrete P-Channel MOSFET to control the power to external logic. A suitable device to do this is an International Rectifier (www.irf.com) IRLML6402, or equivalent. It is recommended that a “soft start” circuit consisting of a 1kΩ series resistor and a 0.1μF capacitor is used to limit the current surge when the MOSFET turns on. Without the soft start circuit it is possible that the transient power surge, caused when the MOSFET switches on, will reset the FT232R or the USB host/hub controller. The soft start circuit example shown in Figure 6.3 powers up with a slew rate of approximaely12.5V/ms. Thus supply voltage to external logic transitions from GND to +5V in approximately 400 microseconds. As an alternative to the MOSFET, a dedicated power switch IC with inbuilt “soft-start” can be used. A suitable power switch IC for such an application is the Micrel (www.micrel.com) MIC2025-2BM or equivalent. With power switching controlled designs the following should be noted: i) The external logic to which the power is being switched should have its own reset circuitry to automatically reset the logic when power is re-applied when moving out of suspend mode. ii) Set the Pull-down on Suspend option in the internal FT232R EEPROM. iii) One of the CBUS Pins should be configured as PWREN# in the internal FT232R EEPROM, and used to switch the power supply to the external circuitry. This should be pulled high through a 10 kΩ resistor. iv) For USB high-power bus powered applications (one that consumes greater than 100mA, and up to 500mA of current from the USB bus), the power consumption of the application must be set in the Max Power field in the internal FT232R EEPROM. A high-power bus powered application uses the descriptor in the internal FT232R EEPROM to inform the system of its power requirements. v) PWREN# gets its VCC from VCCIO. For designs using 3V3 logic, ensure VCCIO is not powered down using the external logic. In this case use the +3V3OUT. Copyright © 2010 Future Technology Devices International Limited 26 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 6.4 USB Bus Powered with Selectable External Logic Supply FT232R A G N D G N D G N D G N D T E S T 100nF 3V3OUT VCCIO NC RESET# NC 10nF TXD RXD RTS# CTS# DTR# DSR# DCD# RI# CBUS0 CBUS3 CBUS2 CBUS1 USBDP USBDM 1 VCC 2 3 4 5 OSCI OSCO CBUS4 Ferrite Bead + SHIELD GND GND GND 3.3V or 5V Supply to External Logic 100nF + 100nF Vcc 4.7uF GND 1 Jumper SLEEP# PWREN# 2 3 Vcc VCCIO 10K VCCIO Figure 6.4 USB Bus Powered with +3.3V or +5V External Logic Power Supply Figure 6.4 illustrates a USB bus power application with selectable external logic supply. The external logic can be selected between +3.3V and +5V using the jumper switch. This jumper is used to allow the FT232R to be interfaced with a +3.3V or +5V logic devices. The VCCIO pin is either supplied with +5V from the USB bus (jumper pins1 and 2 connected), or from the +3.3V output from the FT232R 3V3OUT pin (jumper pins 2 and 3 connected). The supply to VCCIO is also used to supply external logic. With bus powered applications, the following should be noted: i) To comply with the 2.5mA current supply limit during USB suspend mode, PWREN# or SLEEP# signals should be used to power down external logic in this mode. If this is not possible, use the configuration shown in Section 6.3. ii) The maximum current sourced from the USB bus during normal operation should not exceed 100mA, otherwise a bus powered design with power switching (Section 6.3) should be used. Another possible configuration could use a discrete low dropout (LDO) regulator which is supplied by the 5V on the USB bus to supply between +1.8V and +2.8V to the VCCIO pin and to the external logic. In this case VCC would be supplied with the +5V from the USB bus and the VCCIO would be supplied from the output of the LDO regulator. This results in the FT232R I/O pins driving out at between +1.8V and +2.8V logic levels. For a USB bus powered application, it is important to consider the following when selecting the regulator: i) The regulator must be capable of sustaining its output voltage with an input voltage of +4.35V. An Low Drop Out (LDO) regulator should be selected. ii) The quiescent current of the regulator must be low enough to meet the total current requirement of <= 2.5mA during USB suspend mode. A suitable series of LDO regulators that meets these requirements is the MicroChip/Telcom (www.microchip.com) TC55 series of devices. These devices can supply up to 250mA current and have a quiescent current of under 1μA. Copyright © 2010 Future Technology Devices International Limited 27 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 7 Application Examples The following sections illustrate possible applications of the FT232R. The illustrations have omitted pin numbers for ease of understanding since the pins differ between the FT232RL and FT232RQ package options. 7.1 USB to RS232 Converter FT232R GND GND 100nF VCC USBDM USBDP VCCIO NC RESET# NC OSCI OSCO 3V3OUT A G N D G N D G N D G N D T E S T TXD RXD RTS# CTS# DTR# DSR# DCD# RI# CBUS0 CBUS1 CBUS2 CBUS3 CBUS4 1 2 3 4 GND SHIELD GND 100nF 4.7uF + 5 10nF + Ferrite Bead VCC VCC SLEEP# GPIO2 GPIO3 TXD RXD RTS# CTS# DTR# DSR# DCD# RI# RS232 LEVEL CONVERTER TXDATA RXDATA RTS CTS DTR DSR DCD RI TXLED# RXLED# VCC VCC 270R 270R GND RI DTR CTS TXDATA RTS RXDATA DSR DCD DB9M SHIELD 10 5 9 48 3 7 2 6 1 SHDN# Figure 7.1 Application Example showing USB to RS232 Converter An example of using the FT232R as a USB to RS232 converter is illustrated in Figure 7.1. In this application, a TTL to RS232 Level Converter IC is used on the serial UART interface of the FT232R to convert the TTL levels of the FT232R to RS232 levels. This level shift can be done using the popular “213” series of TTL to RS232 level converters. These “213” devices typically have 4 transmitters and 5 receivers in a 28-LD SSOP package and feature an in-built voltage converter to convert the +5V (nominal) VCC to the +/- 9 volts required by RS232. A useful feature of these devices is the SHDN# pin which can be used to power down the device to a low quiescent current during USB suspend mode. A suitable level shifting device is the Sipex SP213EHCA which is capable of RS232 communication at up to 500k baud. If a lower baud rate is acceptable, then several pin compatible alternatives are available such as the Sipex SP213ECA, the Maxim MAX213CAI and the Analogue Devices ADM213E, which are all suitable for communication at up to 115.2k baud. If a higher baud rate is required, the Maxim MAX3245CAI device is capable of RS232 communication rates up to 1Mbaud. Note that the MAX3245 is not pin compatible with the 213 series devices and that the SHDN pin on the MAX device is active high and should be connect to PWREN# pin instead of SLEEP# pin. In example shown, the CBUS0 and CBUS1 have been configured as TXLED# and RXLED# and are being used to drive two LEDs. Copyright © 2010 Future Technology Devices International Limited 28 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 7.2 USB to RS485 Coverter FT232R GND GND 100nF VCC USBDM USBDP VCCIO NC RESET# NC OSCI OSCO 3V3OUT A G N D G N D G N D G N D T E S T TXD RXD RTS# CTS# DTR# DSR# DCD# RI# CBUS0 CBUS1 CBUS2 CBUS3 CBUS4 1 2 3 4 GND SHIELD GND 100nF 4.7uF + 5 10nF + Ferrite Bead Vcc Vcc TXD RXD GND DB9M SHIELD 10 TXDEN GPO PWREN# GPIO0 GPIO1 VCCIO 10K RS485 LEVEL CONVERTER Vcc SP481 5 1 2 3 4 Link 120R 7 6 Figure 7.2 Application Example Showing USB to RS485 Converter An example of using the FT232R as a USB to RS485 converter is shown in Figure 7.2. In this application, a TTL to RS485 level converter IC is used on the serial UART interface of the FT232R to convert the TTL levels of the FT232R to RS485 levels. This example uses the Sipex SP481 device. Equivalent devices are available from Maxim and Analogue Devices. The SP481 is a RS485 device in a compact 8 pin SOP package. It has separate enables on both the transmitter and receiver. With RS485, the transmitter is only enabled when a character is being transmitted from the UART. The TXDEN signal CBUS pin option on the FT232R is provided for exactly this purpose and so the transmitter enable is wired to CBUS2 which has been configured as TXDEN. Similarly, CBUS3 has been configured as PWREN#. This signal is used to control the SP481‟s receiver enable. The receiver enable is active low, so it is wired to the PWREN# pin to disable the receiver when in USB suspend mode. CBUS2 = TXDEN and CBUS3 = PWREN# are the default device configurations of the FT232R pins. RS485 is a multi-drop network; so many devices can communicate with each other over a two wire cable interface. The RS485 cable requires to be terminated at each end of the cable. A link (which provides the 120Ω termination) allows the cable to be terminated if the SP481 is physically positioned at either end of the cable. In this example the data transmitted by the FT232R is also present on the receive path of the SP481.This is a common feature of RS485 and requires the application software to remove the transmitted data from the received data stream. With the FT232R it is possible to do this entirely in hardware by modifying the example shown in Figure 7.2 by logically OR‟ing the FT232R TXDEN and the SP481 receiver output and connecting the output of the OR gate to the RXD of the FT232R. Note that the TXDEN is activated 1 bit period before the start bit. TXDEN is deactivated at the same time as the stop bit. This is not configurable. Copyright © 2010 Future Technology Devices International Limited 29 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 7.3 USB to RS422 Converter FT232R GND GND 100nF VCC USBDM USBDP VCCIO NC RESET# NC OSCI OSCO 3V3OUT A G N D G N D G N D G N D T E S T TXD RXD RTS# CTS# DTR# DSR# DCD# RI# CBUS0 CBUS1 CBUS2 CBUS3 CBUS4- 1 2 3 4 GND SHIELD GND 100nF 4.7uF + 5 10nF + Ferrite Bead Vcc Vcc PWREN# RS422 LEVEL CONVERTER Vcc SP491 5 3 4 6 7 TXDM TXDP RXDP RXDM 120R 10 9 11 12 SLEEP# RS422 LEVEL CONVERTER SP491 3 4 6 7 Vcc Vcc 10K 2 5 120R 11 12 9 10 RTSM RTSP CTSP CTSM GND DB9M SHIELD TXDM TXDP RXDP RXDM RTSM RTSP CTSP CTSM 2 Figure 7.3 USB to RS422 Converter Configuration An example of using the FT232R as a USB to RS422 converter is shown in Figure 7.3. In this application, two TTL to RS422 Level Converter ICs are used on the serial UART interface of the FT232R to convert the TTL levels of the FT232R to RS422 levels. There are many suitable level converter devices available. This example uses Sipex SP491 devices which have enables on both the transmitter and receiver. Since the SP491 transmitter enable is active high, it is connected to a CBUS pin in SLEEP# configuration. The SP491 receiver enable is active low and is therefore connected to a CBUS pin PWREN# configuration. This ensures that when both the SP491 transmitters and receivers are enabled then the device is active, and when the device is in USB suspend mode, the SP491 transmitters and receivers are disabled. If a similar application is used, but the design is USB BUS powered, it may be necessary to use a P-Channel logic level MOSFET (controlled by PWREN#) in the VCC line of the SP491 devices to ensure that the USB standby current of 2.5mA is met. The SP491 is specified to transmit and receive data at a rate of up to 5 Mbaud. In this example the maximum data rate is limited to 3 Mbaud by the FT232R. Copyright © 2010 Future Technology Devices International Limited 30 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 7.4 USB to MCU UART Interface FT232R GND GND 100nF VCC USBDM USBDP VCCIO NC RESET# NC OSCI OSCO 3V3OUT A G N D G N D G N D G N D T E S T TXD RXD RTS# CTS# DTR# DSR# DCD# RI# CBUS0 CBUS1 CBUS2 CBUS3 CBUS4 1 2 3 4 GND SHIELD GND 100nF 4.7uF + 5 10nF + Ferrite Bead Vcc Vcc PWREN# Vcc 12MHz OUT 10K Microcontroller CLK_IN I/O RTS# RXD TXD CTS# Vcc Figure 7.4 USB to MCU UART Interface An example of using the FT232R as a USB to Microcontroller (MCU) UART interface is shown in Figure 7.4. In this application the FT232R uses TXD and RXD for transmission and reception of data, and RTS# / CTS# signals for hardware handshaking. Also in this example CBUS0 has been configured as a 12MHz output to clock the MCU. Optionally, RI# could be connected to another I/O pin on the MCU and used to wake up the USB host controller from suspend mode. If the MCU is handling power management functions, then a CBUS pin can be configured as PWREN# and would also be connected to an I/O pin of the MCU. Copyright © 2010 Future Technology Devices International Limited 31 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 7.5 LED Interface Any of the CBUS I/O pins can be configured to drive an LED. The FT232R has 3 configuration options for driving LEDs from the CBUS. These are TXLED#, RXLED#, and TX&RXLED#. Refer to Section 3.5 for configuration options. FT232R CBUS[0...4] CBUS[0...4] VCCIO TX TXLED# RXLED# RX 270R 270R Figure 7.5 Dual LED Configuration An example of using the FT232R to drive LEDs is shown in Figure 7.5. In this application one of the CBUS pins is used to indicate transmission of data (TXLED#) and another is used to indicate receiving data (RXLED#). When data is being transmitted or received the respective pins will drive from tri-state to low in order to provide indication on the LEDs of data transfer. A digital one-shot is used so that even a small percentage of data transfer is visible to the end user. FT232R CBUS[0...4] TX&RXLED# 270R VCCIO LED Figure 7.6 Single LED Configuration Another example of using the FT232R to drive LEDs is shown in Figure 7.6. In this example one of the CBUS pins is used to indicate when data is being transmitted or received by the device (TX&RXLED). In this configuration the FT232R will drive only a single LED. Copyright © 2010 Future Technology Devices International Limited 32 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 7.6 Using the External Oscillator The FT232R defaults to operating using its own internal oscillator. This requires that the device is powered with VCC(min)=+4.0V. This supply voltage can be taken from the USB VBUS. Applications which require using an external oscillator, VCC= +3.3V, must do so in the following order: 1. When device powered for the very first time, it must have VCC > +4.0V. This supply is available from the USB VBUS supply = +5.0V. 2. The EEPROM must then be programmed to enable external oscillator. This EEPROM modification cannot be done using the FTDI programming utility, MPROG. The EEPROM can only be re-configured from a custom application. Please refer to the following applications note on how to do this: http://www.ftdichip.com/Documents/AppNotes/AN_100_Using_The_FT232_245R_With_External_Osc(FT_000067).pdf 3. The FT232R can then be powered from VCC=+3.3V and an external oscillator. This can be done using a link to switch the VCC supply. The FT232R will fail to operate when the internal oscillator has been disabled, but no external oscillator has been connected. Copyright © 2010 Future Technology Devices International Limited 33 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 8 Internal EEPROM Configuration Following a power-on reset or a USB reset the FT232R will scan its internal EEPROM and read the USB configuration descriptors stored there. The default factory programmed values of the internal EEPROM are shown in Table 8.1. Parameter Value Notes USB Vendor ID (VID) 0403h FTDI default VID (hex) USB Product UD (PID) 6001h FTDI default PID (hex) Serial Number Enabled? Yes Serial Number See Note A unique serial number is generated and programmed into the EEPROM during device final test. Pull down I/O Pins in USB Suspend Disabled Enabling this option will make the device pull down on the UART interface lines when in USB suspend mode (PWREN# is high). Manufacturer Name FTDI Product Description FT232R USB UART Max Bus Power Current 90mA Power Source Bus Powered Device Type FT232R USB Version 0200 Returns USB 2.0 device description to the host. Note: The device is a USB 2.0 Full Speed device (12Mb/s) as opposed to a USB 2.0 High Speed device (480Mb/s). Remote Wake Up Enabled Taking RI# low will wake up the USB host controller from suspend in approximately 20 ms. High Current I/Os Disabled Enables the high drive level on the UART and CBUS I/O pins. Load VCP Driver Enabled Makes the device load the VCP driver interface for the device. CBUS0 TXLED# Default configuration of CBUS0 – Transmit LED drive. CBUS1 RXLED# Default configuration of CBUS1 – Receive LED drive. CBUS2 TXDEN Default configuration of CBUS2 – Transmit data enable for RS485 CBUS3 PWREN# Default configuration of CBUS3 – Power enable. Low after USB enumeration, high during USB suspend mode. Copyright © 2010 Future Technology Devices International Limited 34 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Parameter Value Notes CBUS4 SLEEP# Default configuration of CBUS4 – Low during USB suspend mode. Invert TXD Disabled Signal on this pin becomes TXD# if enable. Invert RXD Disabled Signal on this pin becomes RXD# if enable. Invert RTS# Disabled Signal on this pin becomes RTS if enable. Invert CTS# Disabled Signal on this pin becomes CTS if enable. Invert DTR# Disabled Signal on this pin becomes DTR if enable. Invert DSR# Disabled Signal on this pin becomes DSR if enable. Invert DCD# Disabled Signal on this pin becomes DCD if enable. Invert RI# Disabled Signal on this pin becomes RI if enable. Table 8.1 Default Internal EEPROM Configuration The internal EEPROM in the FT232R can be programmed over USB using the FTDI utility program MPROG. MPROG can be downloaded from FTDI Utilities on the FTDI website (www.ftdichip.com). Version 2.8a or later is required for the FT232R chip. Users who do not have their own USB Vendor ID but who would like to use a unique Product ID in their design can apply to FTDI for a free block of unique PIDs. Contact FTDI support for this service. Copyright © 2010 Future Technology Devices International Limited 35 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 9 Package Parameters The FT232R is available in two different packages. The FT232RL is the SSOP-28 option and the FT232RQ is the QFN-32 package option. The solder reflow profile for both packages is described in Section 9.5. 9.1 SSOP-28 Package Dimensions Figure 9.1 SSOP-28 Package Dimensions The FT232RL is supplied in a RoHS compliant 28 pin SSOP package. The package is lead (Pb) free and uses a „green‟ compound. The package is fully compliant with European Union directive 2002/95/EC. This package is nominally 5.30mm x 10.20mm body (7.80mm x 10.20mm including pins). The pins are on a 0.65 mm pitch. The above mechanical drawing shows the SSOP-28 package. All dimensions are in millimetres. The date code format is YYXX where XX = 2 digit week number, YY = 2 digit year number. This is followed by the revision number. The code XXXXXXXXXXXX is the manufacturing LOT code. This only applies to devices manufactured after April 2009. 12° Typ0° - 8°0.25 0.75 +/-0.200.092.00 Max1.75+/- 0.100.05 Min1.25 +/-0.12FT232RLYYXX-A1141528FTDI5.30 +/-0.307.80 +/-0.40 10.20 +/-0.301.02 Typ.0.30 +/-0.0120.65 +/-0.026XXXXXXXXXXXX Copyright © 2010 Future Technology Devices International Limited 36 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 9.2 QFN-32 Package Dimensions Figure 9.2 QFN-32 Package Dimensions The FT232RQ is supplied in a RoHS compliant leadless QFN-32 package. The package is lead ( Pb ) free, and uses a „green‟ compound. The package is fully compliant with European Union directive 2002/95/EC. This package is nominally 5.00mm x 5.00mm. The solder pads are on a 0.50mm pitch. The above mechanical drawing shows the QFN-32 package. All dimensions are in millimetres. The centre pad on the base of the FT232RQ is not internally connected, and can be left unconnected, or connected to ground (recommended). The date code format is YYXX where XX = 2 digit week number, YY = 2 digit year number. The code XXXXXXX is the manufacturing LOT code. This only applies to devices manufactured after April 2009. Indicates Pin #1 (Laser Marked)FT232RQ3225241716981YYXX-A5.000 +/-0.0755.000 +/-0.0753.200 +/-0.1003.200 +/-0.1000.5000.250 +/-0.0500.500 +/-0.0500.150 MaxPin #1 ID0.900 +/-0.1000.200 0.0502134567823242221201918172526272829303132161514131211109Note: The pin #1 ID is connected internally to the device’s central heat sink area . It is recommended to ground the central heat sink area of the device. 0.200 MinDimensions in mm.Central Heat Sink AreaFTDIXXXXXXX Copyright © 2010 Future Technology Devices International Limited 37 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 9.3 QFN-32 Package Typical Pad Layout Figure 9.3 Typical Pad Layout for QFN-32 Package 9.4 QFN-32 Package Typical Solder Paste Diagram 2.5 +/- 0.0375 2.5 +/- 0.0375 Figure 9.4 Typical Solder Paste Diagram for QFN-32 Package 1 17 25 0.500 0.30 0.200 Min 0.500 +/-0.050 0.150 Max 0.20 0.100 3.200 +/-0.100 3.200 +/-0.100 2.50 2.50 Optional GND Connection Optional GND Connection 9 Copyright © 2010 Future Technology Devices International Limited 38 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 9.5 Solder Reflow Profile The FT232R is supplied in Pb free 28 LD SSOP and QFN-32 packages. The recommended solder reflow profile for both package options is shown in Figure 9.5. Figure 9.5 FT232R Solder Reflow Profile The recommended values for the solder reflow profile are detailed in Table 9.1. Values are shown for both a completely Pb free solder process (i.e. the FT232R is used with Pb free solder), and for a non-Pb free solder process (i.e. the FT232R is used with non-Pb free solder). Profile Feature Pb Free Solder Process Non-Pb Free Solder Process Average Ramp Up Rate (Ts to Tp) 3°C / second Max. 3°C / Second Max. Preheat - Temperature Min (Ts Min.) - Temperature Max (Ts Max.) - Time (ts Min to ts Max) 150°C 200°C 60 to 120 seconds 100°C 150°C 60 to 120 seconds Time Maintained Above Critical Temperature TL: - Temperature (TL) - Time (tL) 217°C 60 to 150 seconds 183°C 60 to 150 seconds Peak Temperature (Tp) 260°C 240°C Time within 5°C of actual Peak Temperature (tp) 20 to 40 seconds 20 to 40 seconds Ramp Down Rate 6°C / second Max. 6°C / second Max. Time for T= 25°C to Peak Temperature, Tp 8 minutes Max. 6 minutes Max. Table 9.1 Reflow Profile Parameter Values Critical Zone: whenT is in the rangeT to TTemperature, T ( Degrees C)Time, t (seconds)25PT = 25º C to TtpTpTLtPreheatStLRamp UpLpRampDownT MaxST MinS Copyright © 2010 Future Technology Devices International Limited 39 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 10 Contact Information Head Office – Glasgow, UK Future Technology Devices International Limited Unit 1, 2 Seaward Place Centurion Business Park Glasgow, G41 1HH United Kingdom Tel: +44 (0) 141 429 2777 Fax: +44 (0) 141 429 2758 E-mail (Sales) sales1@ftdichip.com E-mail (Support) support1@ftdichip.com E-mail (General Enquiries) admin1@ftdichip.com Web Site URL http://www.ftdichip.com Web Shop URL http://www.ftdichip.com Branch Office – Taipei, Taiwan Future Technology Devices International Limited (Taiwan) 2F, No 516, Sec. 1 NeiHu Road Taipei 114 Taiwan, R.O.C. Tel: +886 (0) 2 8791 3570 Fax: +886 (0) 2 8791 3576 E-mail (Sales) tw.sales1@ftdichip.com E-mail (Support) tw.support1@ftdichip.com E-mail (General Enquiries) tw.admin1@ftdichip.com Web Site URL http://www.ftdichip.com Branch Office – Hillsboro, Oregon, USA Future Technology Devices International Limited (USA) 7235 NW Evergreen Parkway, Suite 600 Hillsboro, OR 97123-5803 USA Tel: +1 (503) 547 0988 Fax: +1 (503) 547 0987 E-Mail (Sales) us.sales@ftdichip.com E-Mail (Support) us.admin@ftdichip.com Web Site URL http://www.ftdichip.com Branch Office – Shanghai, China Future Technology Devices International Limited (China) Room 408, 317 Xianxia Road, ChangNing District, ShangHai, China Tel: +86 (21) 62351596 Fax: +86(21) 62351595 E-Mail (Sales): cn.sales@ftdichip.com E-Mail (Support): cn.support@ftdichip.com E-Mail (General Enquiries): cn.admin1@ftdichip.com Web Site URL: http://www.ftdichip.com Distributor and Sales Representatives Please visit the Sales Network page of the FTDI Web site for the contact details of our distributor(s) and sales representative(s) in your country. Copyright © 2010 Future Technology Devices International Limited 40 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Appendix A – References Useful Application Notes http://www.ftdichip.com/Documents/AppNotes/AN232R-01_FT232RBitBangModes.pdf http://www.ftdichip.com/Documents/AppNotes/AN_107_AdvancedDriverOptions_AN_000073.pdf http://www.ftdichip.com/Documents/AppNotes/AN232R-02_FT232RChipID.pdf http://www.ftdichip.com/Documents/AppNotes/AN_121_FTDI_Device_EEPROM_User_Area_Usage.pdf http://www.ftdichip.com/Documents/AppNotes/AN_120_Aliasing_VCP_Baud_Rates.pdf http://www.ftdichip.com/Documents/AppNotes/AN_100_Using_The_FT232_245R_With_External_Osc(FT_000067).pdf http://www.ftdichip.com/Resources/Utilities/AN_126_User_Guide_For_FT232_Factory%20test%20utility.pdf http://www.ftdichip.com/Documents/AppNotes/AN232B-05_BaudRates.pdf http://www.ftdichip.com/Documents/InstallGuides.htm Copyright © 2010 Future Technology Devices International Limited 41 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Appendix B - List of Figures and Tables List of Figures Figure 2.1 FT232R Block Diagram ................................................................................................... 4 Figure 3.1 SSOP Package Pin Out and Schematic Symbol .......................................................... 7 Figure 3.2 QFN-32 Package Pin Out and schematic symbol .............................................................. 10 Figure 6.1 Bus Powered Configuration ........................................................................................... 23 Figure 6.2 Self Powered Configuration ........................................................................................... 24 Figure 6.4 USB Bus Powered with +3.3V or +5V External Logic Power Supply .................................... 26 Figure 7.1 Application Example showing USB to RS232 Converter ..................................................... 27 Figure 7.2 Application Example Showing USB to RS485 Converter .................................................... 28 Figure 7.3 USB to RS422 Converter Configuration ........................................................................... 29 Figure 7.4 USB to MCU UART Interface .......................................................................................... 30 Figure 7.5 Dual LED Configuration ................................................................................................ 31 Figure 7.6 Single LED Configuration .............................................................................................. 31 Figure 9.1 SSOP-28 Package Dimensions ....................................................................................... 35 Figure 9.2 QFN-32 Package Dimensions ......................................................................................... 36 Figure 9.3 Typical Pad Layout for QFN-32 Package .......................................................................... 37 Figure 9.4 Typical Solder Paste Diagram for QFN-32 Package ........................................................... 37 Figure 9.5 FT232R Solder Reflow Profile ........................................................................................ 38 List of Tables Table 3.1 USB Interface Group ....................................................................................................... 7 Table 3.2 Power and Ground Group ................................................................................................. 8 Table 3.3 Miscellaneous Signal Group .............................................................................................. 8 Table 3.4 UART Interface and CUSB Group (see note 3) .................................................................... 9 Table 3.5 USB Interface Group ..................................................................................................... 10 Table 3.6 Power and Ground Group ............................................................................................... 11 Table 3.7 Miscellaneous Signal Group ............................................................................................ 11 Table 3.8 UART Interface and CBUS Group (see note 3) .................................................................. 12 Table 3.9 CBUS Configuration Control ........................................................................................... 13 Table 5.1 Absolute Maximum Ratings ............................................................................................ 17 Table 5.2 Operating Voltage and Current ....................................................................................... 18 Table 5.3 UART and CBUS I/O Pin Characteristics (VCCIO = +5.0V, Standard Drive Level) .................. 18 Table 5.4 UART and CBUS I/O Pin Characteristics (VCCIO = +3.3V, Standard Drive Level) .................. 18 Table 5.5 UART and CBUS I/O Pin Characteristics (VCCIO = +2.8V, Standard Drive Level) .................. 19 Table 5.6 UART and CBUS I/O Pin Characteristics (VCCIO = +1.8V, Standard Drive Level) .................. 19 Table 5.7 UART and CBUS I/O Pin Characteristics (VCCIO = +5.0V, High Drive Level) ......................... 19 Table 5.8 UART and CBUS I/O Pin Characteristics (VCCIO = +3.3V, High Drive Level) ......................... 19 Table 5.9 UART and CBUS I/O Pin Characteristics (VCCIO = +2.8V, High Drive Level) ......................... 20 Table 5.10 UART and CBUS I/O Pin Characteristics (VCCIO = +1.8V, High Drive Level) ....................... 20 Copyright © 2010 Future Technology Devices International Limited 42 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Table 5.11 RESET# and TEST Pin Characteristics ............................................................................ 20 Table 5.12 USB I/O Pin (USBDP, USBDM) Characteristics ................................................................. 21 Table 5.13 EEPROM Characteristics ............................................................................................... 21 Table 5.14 Internal Clock Characteristics ....................................................................................... 21 Table 5.15 OSCI, OSCO Pin Characteristics – see Note 1 ................................................................. 22 Table 8.1 Default Internal EEPROM Configuration ............................................................................ 34 Table 9.1 Reflow Profile Parameter Values ..................................................................................... 38 Copyright © 2010 Future Technology Devices International Limited 43 Document No.: FT_000053 FT232R USB UART IC Datasheet Version 2.10 Clearance No.: FTDI# 38 Appendix C - Revision History Document Title: USB UART IC FT232R Document Reference No.: FT_000053 Clearance No.: FTDI# 38 Product Page: http://www.ftdichip.com/FTProducts.htm Document Feedback: Send Feedback Version 0.90 Initial Datasheet Created August 2005 Version 0.96 Revised Pre-release datasheet October 2005 Version 1.00 Full datasheet released December 2005 Version 1.02 Minor revisions to datasheet December 2005 Version 1.03 Manufacturer ID added to default EEPROM configuration; Buffer sizes added January 2006 Version 1.04 QFN-32 Pad layout and solder paste diagrams added January 2006 Version 2.00 Reformatted, updated package info, added notes for 3.3V operation; June 2008 Part numbers, TID; added UART and CBUS characteristics for +1.8V; Corrected RESET#; Added MTTF data; Corrected the input switching threshold and input hysteresis values for VCCIO=5V Version 2.01 Corrected pin-out number in table3.2 for GND pin18. Improved graphics on some Figures. Add packing details. Changed USB suspend current spec from 500uA to 2.5mA Corrected Figure 9.2 QFN dimensions. August 2008 Version 2.02 Corrected Tape and Reel quantities. Added comment “PWREN# should be used with a 10kΩ resistor pull up”. Replaced TXDEN# with TXDEN since it is active high in various places. Added lot number to the device markings. Added 3V3 regulator output tolerance. Clarified VCC operation and added section headed “Using an external Oscillator” Updated company contact information. April 2009 Version 2.03 Corrected the RX/TX buffer definitions to be relative to the USB interface June 2009 Version 2.04 Additional dimensions added to QFN solder profile June 2009 Version 2.05 Modified package dimensions to 5.0 x 5.0 +/-0.075mm. December 2009 and Solder paste diagram to 2.50 x 2.50 +/-0.0375mm Added Windows 7 32, 64 bit driver support Added FT_PROG utility references Added Appendix A-references.Figure 2.1 updated. Updated USB-IF TID for Rev B Version 2.06 Updated section 6.2, Figure 6.2 and the note, May 2010 Updated section 5.3, Table 5.13, EEPROM data retention time Version 2.07 Added USB Certification Logos July 2010 Version 2.08 Updated USB-IF TID for Rev C April 2011 Version 2.09 Corrected Rev C TID number April 2011 Version 2.10 Table 3.9, added clock output frequency within ±0.7% March 2012 Edited Table 3.9, TXLED# and TXLED# Description Added feedback links LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 1 August 2013 LM78XX / LM78XXA 3-Terminal 1 A Positive Voltage Regulator Features • Output Current up to 1 A • Output Voltages: 5, 6, 8, 9, 10, 12, 15, 18, 24 V • Thermal Overload Protection • Short-Circuit Protection • Output Transistor Safe Operating Area Protection Ordering Information(1) Note: 1. Above output voltage tolerance is available at 25°C. Product Number Output Voltage Tolerance Package Operating Temperature Packing Method LM7805CT ±4% TO-220 (Single Gauge) -40°C to +125°C Rail LM7806CT LM7808CT LM7809CT LM7810CT LM7812CT LM7815CT LM7818CT LM7824CT LM7805ACT ±2% 0°C to +125°C LM7809ACT LM7810ACT LM7812ACT LM7815ACT Description The LM78XX series of three-terminal positive regulators is available in the TO-220 package and with several fixed output voltages, making them useful in a wide range of applications. Each type employs internal current limiting, thermal shut-down, and safe operating area protection. If adequate heat sinking is provided, they can deliver over 1 A output current. Although designed primarily as fixedvoltage regulators, these devices can be used with external components for adjustable voltages and currents. 1 1. Input 2. GND 3. Output GND TO-220 (Single Gauge) LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 2 Block Diagram Figure 1. Block Diagram Absolute Maximum Ratings Stresses exceeding the absolute maximum ratings may damage the device. The device may not function or be operable above the recommended operating conditions and stressing the parts to these levels is not recommended. In addition, extended exposure to stresses above the recommended operating conditions may affect device reliability. The absolute maximum ratings are stress ratings only. Values are at TA = 25°C unless otherwise noted. Symbol Parameter Value Unit VI Input Voltage VO = 5 V to 18 V 35 V VO = 24 V 40 RθJC Thermal Resistance, Junction-Case (TO-220) 5 °C/W RθJA Thermal Resistance, Junction-Air (TO-220) 65 °C/W TOPR Operating Temperature Range LM78xx -40 to +125 °C LM78xxA 0 to +125 TSTG Storage Temperature Range - 65 to +150 °C Starting Circuit Input 1 Reference Voltage Current Generator SOA Protection Thermal Protection Series Pass Element Error Amplifier Output 3 GND 2 LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 3 Electrical Characteristics (LM7805) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 10 V, CI = 0.1 μF, unless otherwise specified. Notes: 2. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 3. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 4.80 5.00 5.20 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 7 V to 20 V 4.75 5.00 5.25 Regline Line Regulation(2) TJ = +25°C VI = 7 V to 25 V 4.0 100.0 mV VI = 8 V to 12 V 1.6 50.0 Regload Load Regulation(2) TJ = +25°C IO = 5 mA to 1.5 A 9.0 100.0 mV IO = 250 mA to 750 mA 4.0 50.0 IQ Quiescent Current TJ =+25°C 5.0 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.03 0.50 mA VI = 7 V to 25 V 0.30 1.30 ΔVO/ΔT Output Voltage Drift(3) IO = 5 mA -0.8 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 42.0 μV/VO RR Ripple Rejection(3) f = 120 Hz, VI = 8 V to 18 V 62.0 73.0 dB VDROP Dropout Voltage TJ = +25°C, IO = 1 A 2.0 V RO Output Resistance(3) f = 1 kHz 15.0 mΩ ISC Short-Circuit Current TJ = +25°C, VI = 35 V 230 mA IPK Peak Current(3) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 4 Electrical Characteristics (LM7806) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 11 V, CI = 0.33 μF,CO = 0.1 μF, unless otherwise specified. Notes: 4. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 5. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 5.75 6.00 6.25 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 8.0 V to 21 V 5.70 6.00 6.30 Regline Line Regulation(4) TJ = +25°C VI = 8 V to 25 V 5.0 120 mV VI = 9 V to 13 V 1.5 60.0 Regload Load Regulation(4) TJ = +25°C IO = 5 mA to 1.5 A 9.0 120.0 mV IO = 250 mA to 750 mA 3.0 60.0 IQ Quiescent Current TJ =+25°C 5.0 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 mA VI = 8 V to 25 V 1.3 ΔVO/ΔT Output Voltage Drift(5) IO = 5 mA -0.8 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 45.0 μV/VO RR Ripple Rejection(5) f = 120 Hz, VI = 8 V to 18 V 62.0 73.0 dB VDROP Dropout Voltage TJ = +25°C, IO = 1 A 2.0 V RO Output Resistance(5) f = 1 kHz 19.0 mΩ ISC Short-Circuit Current TJ = +25°C, VI = 35 V 250 mA IPK Peak Current(5) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 5 Electrical Characteristics (LM7808) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 14 V, CI = 0.33 μF, CO = 0.1 μF, unless otherwise specified. Notes: 6. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 7. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 7.7 8.0 8.3 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 10.5 V to 23 V 7.6 8.0 8.4 Regline Line Regulation(6) TJ = +25°C VI = 10.5 V to 25 V 5.0 160.0 mV VI = 11.5 V to 17 V 2.0 80.0 Regload Load Regulation(6) TJ = +25°C IO = 5 mA to 1.5 A 10.0 160.0 mV IO = 250 mA to 750 mA 5.0 80.0 IQ Quiescent Current TJ =+25°C 5.0 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.05 0.50 mA VI = 10.5 V to 25 V 0.5 1.0 ΔVO/ΔT Output Voltage Drift(7) IO = 5 mA -0.8 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 52.0 μV/VO RR Ripple Rejection(7) f = 120 Hz, VI = 11.5 V to 21.5 V 56.0 73.0 dB VDROP Dropout Voltage IO = 1 A, TJ = +25°C 2.0 V RO Output Resistance(7) f = 1 kHz 17.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 230 mA IPK Peak Current(7) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 6 Electrical Characteristics (LM7809) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 15 V, CI = 0.33 μF,CO = 0.1 μF, unless otherwise specified. Notes: 8. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 9. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 8.65 9.00 9.35 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 11.5 V to 24 V 8.60 9.00 9.40 Regline Line Regulation(8) TJ = +25°C VI = 11.5 V to 25 V 6.0 180.0 mV VI = 12 V to 17 V 2.0 90.0 Regload Load Regulation(8) TJ = +25°C IO = 5 mA to 1.5 A 12.0 180.0 mV IO = 250 mA to 750 mA 4.0 90.0 IQ Quiescent Current TJ =+25°C 5.0 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 mA VI = 11.5 V to 26 V 1.3 ΔVO/ΔT Output Voltage Drift(9) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 58.0 μV/VO RR Ripple Rejection(9) f = 120 Hz, VI = 13 V to 23 V 56.0 71.0 dB VDROP Dropout Voltage IO = 1 A, TJ = +25°C 2.0 V RO Output Resistance(9) f = 1 kHz 17.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 250 mA IPK Peak Current(9) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 7 Electrical Characteristics (LM7810) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 16 V, CI = 0.33 μF,CO = 0.1 μF, unless otherwise specified. Notes: 10. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 11. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 9.6 10.0 10.4 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 12.5 V to 25 V 9.5 10.0 10.5 Regline Line Regulation(10) TJ = +25°C VI = 12.5 V to 25 V 10 200 mV VI = 13 V to 25 V 3 100 Regload Load Regulation(10) TJ = +25°C IO = 5 mA to 1.5 A 12 200 mV IO = 250 mA to 750 mA 4 400 IQ Quiescent Current TJ =+25°C 5.1 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 mA VI = 12.5 V to 29 V 1.0 ΔVO/ΔT Output Voltage Drift(11) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 58.0 μV/VO RR Ripple Rejection(11) f = 120 Hz, VI = 13 V to 23 V 56.0 71.0 dB VDROP Dropout Voltage IO = 1 A, TJ = +25°C 2.0 V RO Output Resistance(11) f = 1 kHz 17.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 250 mA IPK Peak Current(11) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 8 Electrical Characteristics (LM7812) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 19 V, CI = 0.33 μF,CO = 0.1 μF, unless otherwise specified. Notes: 12. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 13. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 11.5 12.0 12.5 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 14.5 V to 27 V 11.4 12.0 12.6 Regline Line Regulation(12) TJ = +25°C VI = 14.5 V to 30 V 10 240 mV VI = 16 V to 22 V 3 120 Regload Load Regulation(12) TJ = +25°C IO = 5 mA to 1.5 A 11 240 mV IO = 250 mA to 750 mA 5 120 IQ Quiescent Current TJ =+25°C 5.1 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.1 0.5 mA VI = 14.5 V to 30 V 0.5 1.0 ΔVO/ΔT Output Voltage Drift(13) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 76.0 μV/VO RR Ripple Rejection(13) f = 120 Hz, VI = 15 V to 25 V 55.0 71.0 dB VDROP Dropout Voltage IO = 1 A, TJ = +25°C 2.0 V RO Output Resistance(13) f = 1 kHz 18.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 230 mA IPK Peak Current(13) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 9 Electrical Characteristics (LM7815) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 23 V, CI = 0.33 μF,CO = 0.1 μF, unless otherwise specified. Notes: 14. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 15. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 14.40 15.00 15.60 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 17.5 V to 30 V 14.25 15.00 15.75 Regline Line Regulation(14) TJ = +25°C VI = 17.5 V to 30 V 11 300 mV VI = 20 V to 26 V 3 150 Regload Load Regulation(14) TJ = +25°C IO = 5 mA to 1.5 A 12 300 mV IO = 250 mA to 750 mA 4 150 IQ Quiescent Current TJ =+25°C 5.2 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 mA VI = 17.5 V to 30 V 1.0 ΔVO/ΔT Output Voltage Drift(15) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 90.0 μV/VO RR Ripple Rejection(15) f = 120 Hz, VI = 18.5 V to 28.5 V 54.0 70.0 dB VDROP Dropout Voltage IO = 1 A, TJ =+25°C 2.0 V RO Output Resistance(15) f = 1 kHz 19.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 250 mA IPK Peak Current(15) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 10 Electrical Characteristics (LM7818) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 27 V, CI = 0.33 μF, CO = 0.1 μF, unless otherwise specified. Notes: 16. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 17. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 17.3 18.0 18.7 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 21 V to 33 V 17.1 18.0 18.9 Regline Line Regulation(16) TJ = +25°C VI = 21 V to 33 V 15 360 mV VI = 24 V to 30 V 5 180 Regload Load Regulation(16) TJ = +25°C IO = 5 mA to 1.5 A 15 360 mV IO = 250 mA to 750 mA 5 180 IQ Quiescent Current TJ =+25°C 5.2 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 mA VI = 21 V to 33 V 1.0 ΔVO/ΔT Output Voltage Drift(17) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 110 μV/VO RR Ripple Rejection(17) f = 120 Hz, VI = 22 V to 32 V 53.0 69.0 dB VDROP Dropout Voltage IO = 1 A, TJ =+25°C 2.0 V RO Output Resistance(17) f = 1 kHz 22.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ =+25°C 250 mA IPK Peak Current(17) TJ =+25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 11 Electrical Characteristics (LM7824) Refer to the test circuit, -40°C < TJ < 125°C, IO = 500 mA, VI = 33 V, CI = 0.33 μF, CO = 0.1 μF, unless otherwise specified. Notes: 18. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 19. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 23.00 24.00 25.00 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 27 V to 38 V 22.80 24.00 25.25 Regline Line Regulation(18) TJ = +25°C VI = 27 V to 38 V 17 480 mV VI = 30 V to 36 V 6 240 Regload Load Regulation(18) TJ = +25°C IO = 5 mA to 1.5 A 15 480 mV IO = 250 mA to 750 mA 5 240 IQ Quiescent Current TJ =+25°C 5.2 8.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.1 0.5 mA VI = 27 V to 38 V 0.5 1.0 ΔVO/ΔT Output Voltage Drift(19) IO = 5 mA -1.5 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 6.0 μV/VO RR Ripple Rejection(19) f = 120 Hz, VI = 28 V to 38 V 50.0 67.0 dB VDROP Dropout Voltage IO = 1 A, TJ = +25°C 2.0 V RO Output Resistance(19) f = 1 kHz 28.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 230 mA IPK Peak Current(19) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 12 Electrical Characteristics (LM7805A) Refer to the test circuit, 0°C < TJ < 125°C, IO = 1 A, VI = 10 V, CI = 0.33 μF, CO = 0.1 μF, unless otherwise specified. Notes: 20. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 21. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 4.9 5.0 5.1 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 7.5 V to 20 V 4.8 5.0 5.2 Regline Line Regulation(20) VI = 7.5 V to 25 V, IO = 500 mA 5.0 50.0 mV VI = 8 V to 12 V 3.0 50.0 TJ = +25°C VI = 7.3 V to 20 V 5.0 50.0 VI = 8 V to 12 V 1.5 25.0 Regload Load Regulation(20) TJ = +25°C, IO = 5 mA to 1.5 A 9.0 100.0 IO = 5 mA to 1 A 9.0 100.0 mV IO = 250 mA to 750 mA 4.0 50.0 IQ Quiescent Current TJ =+25°C 5.0 6.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 VI = 8 V to 25 V, IO = 500 mA 0.8 mA VI = 7.5 V to 20 V, TJ = +25°C 0.8 ΔVO/ΔT Output Voltage Drift(21) IO = 5 mA -0.8 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 10.0 μV/VO RR Ripple Rejection(21) f = 120 Hz, VO = 500 mA, VI =8 V to 18 V 68.0 dB VDROP Dropout Voltage IO = 1 A, TJ =+25°C 2.0 V RO Output Resistance(21) f = 1 kHz 17.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ =+25°C 250 mA IPK Peak Current(21) TJ =+25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 13 Electrical Characteristics (LM7809A) Refer to the test circuit, 0°C < TJ < 125°C, IO = 1 A, VI = 15 V, CI = 0.33 μF,CO = 0.1 μF, unless otherwise specified. Notes: 22. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 23. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 8.82 9.00 9.16 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 11.2 V to 24 V 8.65 9.00 9.35 Regline Line Regulation(22) VI = 11.7 V to 25 V, IO = 500 mA 6.0 90.0 mV VI = 12.5 V to 19 V 4.0 45.0 TJ = +25°C VI = 11.5 V to 24 V 6.0 90.0 VI = 12.5 V to 19 V 2.0 45.0 Regload Load Regulation(22) TJ = +25°C, IO = 5 mA to 1.5 A 12.0 100.0 IO = 5 mA to 1 A 12.0 100.0 mV IO = 250 mA to 750 mA 5.0 50.0 IQ Quiescent Current TJ = +25°C 5.0 6.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 VI = 12 V to 25 V, IO = 500 mA 0.8 mA VI = 11.7 V to 25 V, TJ = +25°C 0.8 ΔVO/ΔT Output Voltage Drift(23) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 10.0 μV/VO RR Ripple Rejection(23) f = 120 Hz, VO = 500 mA, VI =12 V to 22 V 62.0 dB VDROP Dropout Voltage IO = 1 A, TJ = +25°C 2.0 V RO Output Resistance(23) f = 1 kHz 17.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 250 mA IPK Peak Current(23) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 14 Electrical Characteristics (LM7810A) Refer to the test circuit, 0°C < TJ < 125°C, IO = 1 A, VI = 16 V, CI = 0.33 μF, CO = 0.1 μF, unless otherwise specified. Notes: 24. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 25. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 9.8 10.0 10.2 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 12.8 V to 25 V 9.6 10.0 10.4 Regline Line Regulation(24) VI = 12.8 V to 26 V, IO = 500 mA 8.0 100.0 mV VI = 13 V to 20 V 4.0 50.0 TJ = +25°C VI = 12.5 V to 25 V 8.0 100.0 VI = 13 V to 20 V 3.0 50.0 Regload Load Regulation(24) TJ = +25°C, IO = 5 mA to 1.5 A 12.0 100.0 IO = 5 mA to 1 A 12.0 100.0 mV IO = 250 mA to 750 mA 5.0 50.0 IQ Quiescent Current TJ =+25°C 5.0 6.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 VI = 12.8 V to 25 V, IO = 500 mA 0.8 mA VI = 13 V to 26 V, TJ = +25°C 0.5 ΔVO/ΔT Output Voltage Drift(25) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 10.0 μV/VO RR Ripple Rejection(25) f = 120 Hz, VO = 500 mA, VI =14 V to 24 V 62.0 dB VDROP Dropout Voltage IO = 1 A, TJ =+25°C 2.0 V RO Output Resistance(25) f = 1 kHz 17.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ =+25°C 250 mA IPK Peak Current(25) TJ =+25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 15 Electrical Characteristics (LM7812A) Refer to the test circuit, 0°C < TJ < 125°C, IO = 1 A, VI = 19 V, CI = 0.33 μF, CO = 0.1 μF, unless otherwise specified. Notes: 26. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 27. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 11.75 12.00 12.25 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 14.8 V to 27 V 11.50 12.00 12.50 Regline Line Regulation(26) VI = 14.8 V to 30 V, IO = 500 mA 10.0 120.0 mV VI = 16 V to 22 V 4.0 120.0 TJ = +25°C VI = 14.5 V to 27 V 10.0 120.0 VI = 16 V to 22 V 3.0 60.0 Regload Load Regulation(26) TJ = +25°C, IO = 5 mA to 1.5 A 12.0 100.0 IO = 5 mA to 1 A 12.0 100.0 mV IO = 250 mA to 750 mA 5.0 50.0 IQ Quiescent Current TJ = +25°C 5.0 6.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 VI = 14 V to 27 V, IO = 500 mA 0.8 mA VI = 15 V to 30 V, TJ = +25°C 0.8 ΔVO/ΔT Output Voltage Drift(27) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 10.0 μV/VO RR Ripple Rejection(27) f = 120 Hz, VO = 500 mA, VI =14 V to 24 V 60.0 dB VDROP Dropout Voltage IO = 1 A, TJ = +25°C 2.0 V RO Output Resistance(27) f = 1 kHz 18.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ = +25°C 250 mA IPK Peak Current(27) TJ = +25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 16 Electrical Characteristics (LM7815A) Refer to the test circuit, 0°C < TJ < 125°C, IO = 1 A, VI = 23 V, CI = 0.33 μF, CO = 0.1 μF, unless otherwise specified. Notes: 28. Load and line regulation are specified at constant junction temperature. Changes in VO due to heating effects must be taken into account separately. Pulse testing with low duty is used. 29. These parameters, although guaranteed, are not 100% tested in production. Symbol Parameter Conditions Min. Typ. Max. Unit VO Output Voltage TJ = +25°C 14.75 15.00 15.30 IO = 5 mA to 1 A, PO ≤ 15 W, V VI = 17.7 V to 30 V 14.40 15.00 15.60 Regline Line Regulation(28) VI = 17.4 V to 30 V, IO = 500 mA 10.0 150.0 mV VI = 20 V to 26 V 5.0 150.0 TJ = +25°C VI = 17.5 V to 30 V 11.0 150.0 VI = 20 V to 26 V 3.0 75.0 Regload Load Regulation(28) TJ = +25°C, IO = 5 mA to 1.5 A 12.0 100.0 IO = 5 mA to 1 A 12.0 100.0 mV IO = 250 mA to 750 mA 5.0 50.0 IQ Quiescent Current TJ =+25°C 5.2 6.0 mA ΔIQ Quiescent Current Change IO = 5 mA to 1 A 0.5 VI = 17.5 V to 30 V, IO = 500 mA 0.8 mA VI = 17.5 V to 30 V, TJ = +25°C 0.8 ΔVO/ΔT Output Voltage Drift(29) IO = 5 mA -1.0 mV/°C VN Output Noise Voltage f = 10 Hz to 100 kHz, TA = +25°C 10.0 μV/VO RR Ripple Rejection(29) f = 120 Hz, VO = 500 mA, VI =18.5 V to 28.5 V 58.0 dB VDROP Dropout Voltage IO = 1 A, TJ =+25°C 2.0 V RO Output Resistance(29) f = 1 kHz 19.0 mΩ ISC Short-Circuit Current VI = 35 V, TJ =+25°C 250 mA IPK Peak Current(29) TJ =+25°C 2.2 A LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 17 Typical Performance Characteristics Figure 2. Quiescent Current Figure 3. Peak Output Current Figure 4. Output Voltage Figure 5. Quiescent Current LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 18 Typical Applications Figure 6. DC Parameters Figure 7. Load Regulation Figure 8. Ripple Rejection CI CO 0.1μF 0.33μF Input Output LM78XX 1 3 2 LM78XX 3 2 1 0.33μF 270pF 100Ω 30μS RL 2N6121 or EQ Input Output VO 0V VO LM78XX Input Output 5.1Ω 0.33μF 2 1 3 RL 470μF 120Hz + LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 19 Figure 9. Fixed-Output Regulator Notes: 29. To specify an output voltage, substitute voltage value for “XX”. A common ground is required between the input and the output voltage. The input voltage must remain typically 2.0 V above the output voltage even during the low point on the input ripple voltage. 30. CI is required if regulator is located an appreciable distance from power supply filter. 31. CO improves stability and transient response. Figure 10. Figure 11. Circuit for Increasing Output Voltage CI CO 0.1μF 0.33μF Input Output LM78XX 1 3 2 CI CO 0.1μF 0.33μF Output Input LM78XX 1 3 2 VXX R1 RL IQ IO IO = R1 +IQ VXX CI CO 0.1μF 0.33μF Output Input LM78XX 1 3 2 VXX R1 R2 IQ IRI ≥ 5 IQ VO = VXX(1 + R2 / R1) + IQR2 LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 20 Figure 12. Adjustable Output Regulator (7 V to 30 V) Figure 13. High-Current Voltage Regulator Figure 14. High Output Current with Short-Circuit Protection LM741 - + 2 3 6 4 2 1 3 CI 0.33μF Input Output 0.1μF CO LM7805 10kΩ IRI ≥ 5 IQ VO = VXX(1 + R2 / R1) + IQR2 3 2 1 LM78XX Output Input R1 3Ω 0.33μF IREG 0.1μF IO IQ1 IO = IREG + BQ1 (IREG–VBEQ1/R1) Q1 BD536 R1 = VBEQ1 IREG–IQ1/ BQ1 LM78XX Output 0.33μF 0.1μF R1 3Ω 3 2 1 Input Q1 Q2 Q1 = TIP42 Q2 = TIP42 RSC = I SC VBEQ2 RSC LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 21 Figure 15. Tracking Voltage Regulator Figure 16. Split Power Supply (±15 V - 1 A) LM78XX LM741 0.33μF 0.1μF 1 2 3 7 2 6 4 3 4.7kΩ 4.7kΩ TIP42 COMMON COMMON VO -VO VI -VIN _ + 1 3 2 1 2 3 0.33μF 0.1μF 2.2μF 1μF + + 1N4001 1N4001 +15V -15V +20V -20V LM7815 MC7915 LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 22 Figure 17. Negative Output Voltage Circuit Figure 18. Switching Regulator LM78XX Output Input + 1 2 0.1μF 3 LM78XX 1mH 1 3 2 2000μF Input Output D45H11 0.33μF 470Ω 4.7Ω 10μF 0.5Ω Z1 + + LM78XX / LM78XXA — 3-Terminal 1 A Positive Voltage Regulator © 2006 Fairchild Semiconductor Corporation www.fairchildsemi.com LM78XX / LM78XXA Rev. 1.3.0 23 Physical Dimensions Figure 19. TO-220, MOLDED, 3-LEAD, JEDEC VARIATION AB (ACTIVE) Package drawings are provided as a service to customers considering Fairchild components. Drawings may change in any manner without notice. Please note the revision and/or date on the drawing and contact a Fairchild Semiconductor representative to verify or obtain the most recent revision. Package specifications do not expand the terms of Fairchild’s worldwide terms and conditions, specifically the warranty therein, which covers Fairchild products. Always visit Fairchild Semiconductor’s online packaging area for the most recent package drawings: http://www.fairchildsemi.com/dwg/TO/TO220B03.pdf. For current tape and reel specifications, visit Fairchild Semiconductor’s online packaging area: http://www.fairchildsemi.com/packing_dwg/PKG-TO220B03_TC.pdf. TO-220 (SINGLE GAUGE) © Fairchild Semiconductor Corporation www.fairchildsemi.com TRADEMARKS The following includes registered and unregistered trademarks and service marks, owned by Fairchild Semiconductor and/or its global subsidiaries, and is not intended to be an exhaustive list of all such trademarks. 2Cool AccuPower AX-CAP®* BitSiC Build it Now CorePLUS CorePOWER CROSSVOLT CTL Current Transfer Logic DEUXPEED® Dual Cool™ EcoSPARK® EfficientMax ESBC Fairchild® Fairchild Semiconductor® FACT Quiet Series FACT® FAST® FastvCore FETBench FPS F-PFS FRFET® Global Power ResourceSM GreenBridge Green FPS Green FPS e-Series Gmax GTO IntelliMAX ISOPLANAR Making Small Speakers Sound Louder and Better™ MegaBuck MICROCOUPLER MicroFET MicroPak MicroPak2 MillerDrive MotionMax mWSaver® OptoHiT OPTOLOGIC® OPTOPLANAR® ® PowerTrench® PowerXS™ Programmable Active Droop QFET® QS Quiet Series RapidConfigure  Saving our world, 1mW/W/kW at a time™ SignalWise SmartMax SMART START Solutions for Your Success SPM® STEALTH SuperFET® SuperSOT-3 SuperSOT-6 SuperSOT-8 SupreMOS® SyncFET Sync-Lock™ ®* TinyBoost® TinyBuck® TinyCalc TinyLogic® TINYOPTO TinyPower TinyPWM TinyWire TranSiC TriFault Detect TRUECURRENT®* SerDes UHC® Ultra FRFET UniFET VCX VisualMax VoltagePlus XS™ * Trademarks of System General Corporation, used under license by Fairchild Semiconductor. DISCLAIMER FAIRCHILD SEMICONDUCTOR RESERVES THE RIGHT TO MAKE CHANGES WITHOUT FURTHER NOTICE TO ANY PRODUCTS HEREIN TO IMPROVE RELIABILITY, FUNCTION, OR DESIGN. FAIRCHILD DOES NOT ASSUME ANY LIABILITY ARISING OUT OF THE APPLICATION OR USE OF ANY PRODUCT OR CIRCUIT DESCRIBED HEREIN; NEITHER DOES IT CONVEY ANY LICENSE UNDER ITS PATENT RIGHTS, NOR THE RIGHTS OF OTHERS. THESE SPECIFICATIONS DO NOT EXPAND THE TERMS OF FAIRCHILD’S WORLDWIDE TERMS AND CONDITIONS, SPECIFICALLY THE WARRANTY THEREIN, WHICH COVERS THESE PRODUCTS. LIFE SUPPORT POLICY FAIRCHILD’S PRODUCTS ARE NOT AUTHORIZED FOR USE AS CRITICAL COMPONENTS IN LIFE SUPPORT DEVICES OR SYSTEMS WITHOUT THE EXPRESS WRITTEN APPROVAL OF FAIRCHILD SEMICONDUCTOR CORPORATION. As used herein: 1. Life support devices or systems are devices or systems which, (a) are intended for surgical implant into the body or (b) support or sustain life, and (c) whose failure to perform when properly used in accordance with instructions for use provided in the labeling, can be reasonably expected to result in a significant injury of the user. 2. A critical component in any component of a life support, device, or system whose failure to perform can be reasonably expected to cause the failure of the life support device or system, or to affect its safety or effectiveness. ANTI-COUNTERFEITING POLICY Fairchild Semiconductor Corporation's Anti-Counterfeiting Policy. Fairchild's Anti-Counterfeiting Policy is also stated on our external website, www.fairchildsemi.com, under Sales Support. Counterfeiting of semiconductor parts is a growing problem in the industry. All manufacturers of semiconductor products are experiencing counterfeiting of their parts. Customers who inadvertently purchase counterfeit parts experience many problems such as loss of brand reputation, substandard performance, failed applications, and increased cost of production and manufacturing delays. Fairchild is taking strong measures to protect ourselves and our customers from the proliferation of counterfeit parts. Fairchild strongly encourages customers to purchase Fairchild parts either directly from Fairchild or from Authorized Fairchild Distributors who are listed by country on our web page cited above. Products customers buy either from Fairchild directly or from Authorized Fairchild Distributors are genuine parts, have full traceability, meet Fairchild's quality standards for handling and storage and provide access to Fairchild's full range of up-to-date technical and product information. Fairchild and our Authorized Distributors will stand behind all warranties and will appropriately address any warranty issues that may arise. Fairchild will not provide any warranty coverage or other assistance for parts bought from Unauthorized Sources. Fairchild is committed to combat this global problem and encourage our customers to do their part in stopping this practice by buying direct or from authorized distributors. PRODUCT STATUS DEFINITIONS Definition of Terms Datasheet Identification Product Status Definition Advance Information Formative / In Design Datasheet contains the design specifications for product development. Specifications may change in any manner without notice. Preliminary First Production Datasheet contains preliminary data; supplementary data will be published at a later date. Fairchild Semiconductor reserves the right to make changes at any time without notice to improve design. No Identification Needed Full Production Datasheet contains final specifications. Fairchild Semiconductor reserves the right to make changes at any time without notice to improve the design. Obsolete Not In Production Datasheet contains specifications on a product that is discontinued by Fairchild Semiconductor. The datasheet is for reference information only. Rev. I65 ® Low Cost Low Power Instrumentation Amplifier AD620 Rev. H Information furnished by Analog Devices is believed to be accurate and reliable. However, no responsibility is assumed by Analog Devices for its use, nor for any infringements of patents or other rights of third parties that may result from its use. Specifications subject to change without notice. No license is granted by implication or otherwise under any patent or patent rights of Analog Devices. Trademarks and registered trademarks are the property of their respective owners. One Technology Way, P.O. Box 9106, Norwood, MA 02062-9106, U.S.A. Tel: 781.329.4700 www.analog.com Fax: 781.326.8703© 2003–2011 Analog Devices, Inc. All rights reserved. FEATURES Easy to use Gain set with one external resistor (Gain range 1 to 10,000) Wide power supply range (±2.3 V to ±18 V) Higher performance than 3 op amp IA designs Available in 8-lead DIP and SOIC packaging Low power, 1.3 mA max supply current Excellent dc performance (B grade) 50 μV max, input offset voltage 0.6 μV/°C max, input offset drift 1.0 nA max, input bias current 100 dB min common-mode rejection ratio (G = 10) Low noise 9 nV/√Hz @ 1 kHz, input voltage noise 0.28 μV p-p noise (0.1 Hz to 10 Hz) Excellent ac specifications 120 kHz bandwidth (G = 100) 15 μs settling time to 0.01% APPLICATIONS Weigh scales ECG and medical instrumentation Transducer interface Data acquisition systems Industrial process controls Battery-powered and portable equipment CONNECTION DIAGRAM –IN RG –VS +IN RG +VS OUTPUT REF 1 2 3 4 8 7 6 AD620 5 TOP VIEW 00775-0-001 Figure 1. 8-Lead PDIP (N), CERDIP (Q), and SOIC (R) Packages PRODUCT DESCRIPTION The AD620 is a low cost, high accuracy instrumentation amplifier that requires only one external resistor to set gains of 1 to 10,000. Furthermore, the AD620 features 8-lead SOIC and DIP packaging that is smaller than discrete designs and offers lower power (only 1.3 mA max supply current), making it a good fit for battery-powered, portable (or remote) applications. The AD620, with its high accuracy of 40 ppm maximum nonlinearity, low offset voltage of 50 μV max, and offset drift of 0.6 μV/°C max, is ideal for use in precision data acquisition systems, such as weigh scales and transducer interfaces. Furthermore, the low noise, low input bias current, and low power of the AD620 make it well suited for medical applications, such as ECG and noninvasive blood pressure monitors. The low input bias current of 1.0 nA max is made possible with the use of Superϐeta processing in the input stage. The AD620 works well as a preamplifier due to its low input voltage noise of 9 nV/√Hz at 1 kHz, 0.28 μV p-p in the 0.1 Hz to 10 Hz band, and 0.1 pA/√Hz input current noise. Also, the AD620 is well suited for multiplexed applications with its settling time of 15 μs to 0.01%, and its cost is low enough to enable designs with one in-amp per channel. Table 1. Next Generation Upgrades for AD620 Part Comment AD8221 Better specs at lower price AD8222 Dual channel or differential out AD8226 Low power, wide input range AD8220 JFET input AD8228 Best gain accuracy AD8295 +2 precision op amps or differential out AD8429 Ultra low noise 0 5 10 15 20 30,000 5,000 10,000 15,000 20,000 25,000 0 TOTAL ERROR, PPM OF FULL SCALE SUPPLY CURRENT (mA) AD620A RG 3 OP AMP IN-AMP (3 OP-07s) 00775-0-002 Figure 2. Three Op Amp IA Designs vs. AD620 IMPORTANT LINKS for the AD620* Last content update 01/08/2014 09:49 am Looking for a high performance in-amp with lower noise, wider bandwidth, and fast settling time? Consider the AD8421 Looking for a high performance in-amp with lower power and a rail-to-rail output? Consider the AD8422. DOCUMENTATION AD620: Military Data Sheet AN-282: Fundamentals of Sampled Data Systems AN-244: A User's Guide to I.C. Instrumentation Amplifiers AN-245: Instrumentation Amplifiers Solve Unusual Design Problems AN-671: Reducing RFI Rectification Errors in In-Amp Circuits AN-589: Ways to Optimize the Performance of a Difference Amplifier A Designer's Guide to Instrumentation Amplifiers (3rd Edition) UG-261: Evaluation Boards for the AD62x, AD822x and AD842x Series ECG Front-End Design is Simplified with MicroConverter Low-Power, Low-Voltage IC Choices for ECG System Requirements Ask The Applications Engineer-10 Auto-Zero Amplifiers High-performance Adder Uses Instrumentation Amplifiers Protecting Instrumentation Amplifiers Input Filter Prevents Instrumentation-amp RF-Rectification Errors The AD8221 - Setting a New Industry Standard for Instrumentation Amplifiers ADI Warns Against Misuse of COTS Integrated Circuits Space Qualified Parts List Applying Instrumentation Amplifiers Effectively: The Importance of an Input Ground Return Leading Inside Advertorials: Applying Instrumentation Amplifiers Effectively–The Importance of an Input Ground Return DESIGN TOOLS, MODELS, DRIVERS & SOFTWARE In-Amp Error Calculator These tools will help estimate error contributions in your instrumentation amplifier circuit. It uses input parameters such as temperature, gain, voltage input, and source impedance to determine the errors that can contribute to your overall design. In-Amp Common Mode Calculator AD620 SPICE Macro-Model AD620A SPICE Macro-Model AD620B SPICE Macro-Model AD620S SPICE Macro-Model AD620 SABER Macro-Model Conv, 10/00 EVALUATION KITS & SYMBOLS & FOOTPRINTS View the Evaluation Boards and Kits page for documentation and purchasing Symbols and Footprints PRODUCT RECOMMENDATIONS & REFERENCE DESIGNS CN-0146: Low Cost Programmable Gain Instrumentation Amplifier Circuit Using the ADG1611 Quad SPST Switch and AD620 Instrumentation Amplifier DESIGN COLLABORATION COMMUNITY Collaborate Online with the ADI support team and other designers about select ADI products. Follow us on Twitter: www.twitter.com/ADI_News Like us on Facebook: www.facebook.com/AnalogDevicesInc DESIGN SUPPORT Submit your support request here: Linear and Data Converters Embedded Processing and DSP Telephone our Customer Interaction Centers toll free: Americas: 1-800-262-5643 Europe: 00800-266-822-82 China: 4006-100-006 India: 1800-419-0108 Russia: 8-800-555-45-90 Quality and Reliability Lead(Pb)-Free Data SAMPLE & BUY AD620 View Price & Packaging Request Evaluation Board Request Samples Check Inventory & Purchase Find Local Distributors * This page was dynamically generated by Analo g Devices, Inc. and inserted into this data sheet. Note: Dynamic changes to the content on this page (labeled 'Important Links') does not constitute a change to the revision number of the product data sheet. This content may be frequently modified. Powered by TCPDF (www.tcpdf.org) AD620 Rev. H | Page 2 of 20 TABLE OF CONTENTS Specifications .....................................................................................3 Absolute Maximum Ratings ............................................................5 ESD Caution ..................................................................................5 Typical Performance Characteristics..............................................6 Theory of Operation.......................................................................12 Gain Selection..............................................................................15 Input and Output Offset Voltage ..............................................15 Reference Terminal .....................................................................15 Input Protection ..........................................................................15 RF Interference............................................................................15 Common-Mode Rejection.........................................................16 Grounding....................................................................................16 Ground Returns for Input Bias Currents.................................17 AD620ACHIPS Information.........................................................18 Outline Dimensions........................................................................19 Ordering Guide ...........................................................................20 REVISION HISTORY 7/11—Rev. G to Rev. H Deleted Figure 3.................................................................................1 Added Table 1 ....................................................................................1 Moved Figure 2 ..................................................................................1 Added ESD Input Diodes to Simplified Schematic ....................12 Changes to Input Protection Section............................................15 Added Figure 41; Renumbered Sequentially ...............................15 Changes to AD620ACHIPS Information Section ......................18 Updated Ordering Guide ...............................................................20 12/04—Rev. F to Rev. G Updated Format..................................................................Universal Change to Features............................................................................1 Change to Product Description.......................................................1 Changes to Specifications.................................................................3 Added Metallization Photograph....................................................4 Replaced Figure 4-Figure 6 ..............................................................6 Replaced Figure 15............................................................................7 Replaced Figure 33..........................................................................10 Replaced Figure 34 and Figure 35.................................................10 Replaced Figure 37..........................................................................10 Changes to Table 3 ..........................................................................13 Changes to Figure 41 and Figure 42 .............................................14 Changes to Figure 43 ......................................................................15 Change to Figure 44 ........................................................................17 Changes to Input Protection section ............................................15 Deleted Figure 9 ..............................................................................15 Changes to RF Interference section..............................................15 Edit to Ground Returns for Input Bias Currents section...........17 Added AD620CHIPS to Ordering Guide ....................................19 7/03—Data Sheet Changed from Rev. E to Rev. F Edit to FEATURES............................................................................1 Changes to SPECIFICATIONS.......................................................2 Removed AD620CHIPS from ORDERING GUIDE ...................4 Removed METALLIZATION PHOTOGRAPH...........................4 Replaced TPCs 1–3 ...........................................................................5 Replaced TPC 12...............................................................................6 Replaced TPC 30...............................................................................9 Replaced TPCs 31 and 32...............................................................10 Replaced Figure 4............................................................................10 Changes to Table I...........................................................................11 Changes to Figures 6 and 7 ............................................................12 Changes to Figure 8 ........................................................................13 Edited INPUT PROTECTION section........................................13 Added new Figure 9........................................................................13 Changes to RF INTERFACE section ............................................14 Edit to GROUND RETURNS FOR INPUT BIAS CURRENTS section...............................................................................................15 Updated OUTLINE DIMENSIONS.............................................16 AD620 Rev. H | Page 3 of 20 SPECIFICATIONS Typical @ 25°C, VS = ±15 V, and RL = 2 kΩ, unless otherwise noted. Table 2. Parameter Conditions AD620A AD620B AD620S1 Min Typ Max Min Typ Max Min Typ Max Unit GAIN G = 1 + (49.4 kΩ/RG) Gain Range 1 10,000 1 10,000 1 10,000 Gain Error2 VOUT = ±10 V G = 1 0.03 0.10 0.01 0.02 0.03 0.10 % G = 10 0.15 0.30 0.10 0.15 0.15 0.30 % G = 100 0.15 0.30 0.10 0.15 0.15 0.30 % G = 1000 0.40 0.70 0.35 0.50 0.40 0.70 % Nonlinearity VOUT = −10 V to +10 V G = 1–1000 RL = 10 kΩ 10 40 10 40 10 40 ppm G = 1–100 RL = 2 kΩ 10 95 10 95 10 95 ppm Gain vs. Temperature G = 1 10 10 10 ppm/°C Gain >12 −50 −50 −50 ppm/°C VOLTAGE OFFSET (Total RTI Error = VOSI + VOSO/G) Input Offset, VOSI VS = ±5 V to ± 15 V 30 125 15 50 30 125 μV Overtemperature VS = ±5 V to ± 15 V 185 85 225 μV Average TC VS = ±5 V to ± 15 V 0.3 1.0 0.1 0.6 0.3 1.0 μV/°C Output Offset, VOSO VS = ±15 V 400 1000 200 500 400 1000 μV VS = ± 5 V 1500 750 1500 μV Overtemperature VS = ±5 V to ± 15 V 2000 1000 2000 μV Average TC VS = ±5 V to ± 15 V 5.0 15 2.5 7.0 5.0 15 μV/°C Offset Referred to the Input vs. Supply (PSR) VS = ±2.3 V to ±18 V G = 1 80 100 80 100 80 100 dB G = 10 95 120 100 120 95 120 dB G = 100 110 140 120 140 110 140 dB G = 1000 110 140 120 140 110 140 dB INPUT CURRENT Input Bias Current 0.5 2.0 0.5 1.0 0.5 2 nA Overtemperature 2.5 1.5 4 nA Average TC 3.0 3.0 8.0 pA/°C Input Offset Current 0.3 1.0 0.3 0.5 0.3 1.0 nA Overtemperature 1.5 0.75 2.0 nA Average TC 1.5 1.5 8.0 pA/°C INPUT Input Impedance Differential 10||2 10||2 10||2 GΩ_pF Common-Mode 10||2 10||2 10||2 GΩ_pF Input Voltage Range3 VS = ±2.3 V to ±5 V −VS + 1.9 +VS − 1.2 −VS + 1.9 +VS − 1.2 −VS + 1.9 +VS − 1.2 V Overtemperature −VS + 2.1 +VS − 1.3 −VS + 2.1 +VS − 1.3 −VS + 2.1 +VS − 1.3 V VS = ± 5 V to ±18 V −VS + 1.9 +VS − 1.4 −VS + 1.9 +VS − 1.4 −VS + 1.9 +VS − 1.4 V Overtemperature −VS + 2.1 +VS − 1.4 −VS + 2.1 +VS + 2.1 −VS + 2.3 +VS − 1.4 V AD620 Rev. H | Page 4 of 20 AD620A AD620B AD620S1 Parameter Conditions Min Typ Max Min Typ Max Min Typ Max Unit Common-Mode Rejection Ratio DC to 60 Hz with 1 kΩ Source Imbalance VCM = 0 V to ± 10 V G = 1 73 90 80 90 73 90 dB G = 10 93 110 100 110 93 110 dB G = 100 110 130 120 130 110 130 dB G = 1000 110 130 120 130 110 130 dB OUTPUT Output Swing RL = 10 kΩ VS = ±2.3 V to ± 5 V −VS + 1.1 +VS − 1.2 −VS + 1.1 +VS − 1.2 −VS + 1.1 +VS − 1.2 V Overtemperature −VS + 1.4 +VS − 1.3 −VS + 1.4 +VS − 1.3 −VS + 1.6 +VS − 1.3 V VS = ±5 V to ± 18 V −VS + 1.2 +VS − 1.4 −VS + 1.2 +VS − 1.4 −VS + 1.2 +VS − 1.4 V Overtemperature −VS + 1.6 +VS – 1.5 −VS + 1.6 +VS – 1.5 –VS + 2.3 +VS – 1.5 V Short Circuit Current ±18 ±18 ±18 mA DYNAMIC RESPONSE Small Signal –3 dB Bandwidth G = 1 1000 1000 1000 kHz G = 10 800 800 800 kHz G = 100 120 120 120 kHz G = 1000 12 12 12 kHz Slew Rate 0.75 1.2 0.75 1.2 0.75 1.2 V/μs Settling Time to 0.01% 10 V Step G = 1–100 15 15 15 μs G = 1000 150 150 150 μs NOISE Voltage Noise, 1 kHz Total RTI Noise (e2 ) (e /G)2 = ni + no Input, Voltage Noise, eni 9 13 9 13 9 13 nV/√Hz Output, Voltage Noise, eno 72 100 72 100 72 100 nV/√Hz RTI, 0.1 Hz to 10 Hz G = 1 3.0 3.0 6.0 3.0 6.0 μV p-p G = 10 0.55 0.55 0.8 0.55 0.8 μV p-p G = 100–1000 0.28 0.28 0.4 0.28 0.4 μV p-p Current Noise f = 1 kHz 100 100 100 fA/√Hz 0.1 Hz to 10 Hz 10 10 10 pA p-p REFERENCE INPUT RIN 20 20 20 kΩ IIN VIN+, VREF = 0 50 60 50 60 50 60 μA Voltage Range −VS + 1.6 +VS − 1.6 −VS + 1.6 +VS − 1.6 −VS + 1.6 +VS − 1.6 V Gain to Output 1 ± 0.0001 1 ± 0.0001 1 ± 0.0001 POWER SUPPLY Operating Range4 ±2.3 ±18 ±2.3 ±18 ±2.3 ±18 V Quiescent Current VS = ±2.3 V to ±18 V 0.9 1.3 0.9 1.3 0.9 1.3 mA Overtemperature 1.1 1.6 1.1 1.6 1.1 1.6 mA TEMPERATURE RANGE For Specified Performance −40 to +85 −40 to +85 −55 to +125 °C 1 See Analog Devices military data sheet for 883B tested specifications. 2 Does not include effects of external resistor RG. 3 One input grounded. G = 1. 4 This is defined as the same supply range that is used to specify PSR. AD620 Rev. H | Page 5 of 20 ABSOLUTE MAXIMUM RATINGS Table 3. Parameter Rating Supply Voltage ±18 V Internal Power Dissipation1 650 mW Input Voltage (Common-Mode) ±VS Differential Input Voltage 25 V Output Short-Circuit Duration Indefinite Storage Temperature Range (Q) −65°C to +150°C Storage Temperature Range (N, R) −65°C to +125°C Operating Temperature Range AD620 (A, B) −40°C to +85°C AD620 (S) −55°C to +125°C Lead Temperature Range (Soldering 10 seconds) 300°C 1 Specification is for device in free air: 8-Lead Plastic Package: θJA = 95°C 8-Lead CERDIP Package: θJA = 110°C 8-Lead SOIC Package: θJA = 155°C Stresses above those listed under Absolute Maximum Ratings may cause permanent damage to the device. This is a stress rating only; functional operation of the device at these or any other condition s above those indicated in the operational section of this specification is not implied. Exposure to absolute maximum rating conditions for extended periods may affect device reliability. ESD CAUTION AD620 Rev. H | Page 6 of 20 TYPICAL PERFORMANCE CHARACTERISTICS (@ 25°C, VS = ±15 V, RL = 2 kΩ, unless otherwise noted.) INPUT OFFSET VOLTAGE (μV) 20 30 40 50 –40 0 40 80 PERCENTAGE OF UNITS –80 SAMPLE SIZE = 360 10 0 00775-0-005 Figure 3. Typical Distribution of Input Offset Voltage INPUT BIAS CURRENT (pA) 0 10 20 30 40 50 –600 0 600 PERCENTAGE OF UNITS –1200 1200 SAMPLE SIZE = 850 00775-0-006 Figure 4. Typical Distribution of Input Bias Current 10 20 30 40 50 –200 0 200 400 INPUT OFFSET CURRENT (pA) PERCENTAGE OF UNITS –400 0 SAMPLE SIZE = 850 00775-0-007 Figure 5. Typical Distribution of Input Offset Current TEMPERATURE (°C) INPUT BIAS CURRENT (nA) +IB –IB 2.0 –2.0 175 –1.0 –1.5 –75 –0.5 0 0.5 1.0 1.5 –25 25 75 125 00775-0-008 Figure 6. Input Bias Current vs. Temperature CHANGE IN OFFSET VOLTAGE (μV) 1.5 0.5 WARM-UP TIME (Minutes) 2.0 0 0 1 1.0 2 3 4 5 00775-0-009 Figure 7. Change in Input Offset Voltage vs. Warm-Up Time FREQUENCY (Hz) 1000 1 1 100k 100 10 100 1k 10k VOLTAGE NOISE (nV/ Hz) GAIN = 1 GAIN = 10 10 GAIN = 100, 1,000 GAIN = 1000 BW LIMIT 00775-0-010 Figure 8. Voltage Noise Spectral Density vs. Frequency (G = 1−1000) AD620 Rev. H | Page 7 of 20 FREQUENCY (Hz) 1000 100 10 1 10 100 1000 CURRENT NOISE (fA/ Hz) 00775-0-011 Figure 9. Current Noise Spectral Density vs. Frequency RTI NOISE (2.0μV/DIV) TIME (1 SEC/DIV) 00775-0-012 Figure 10. 0.1 Hz to 10 Hz RTI Voltage Noise (G = 1) RTI NOISE (0.1μV/DIV) TIME (1 SEC/DIV) 00775-0-013 Figure 11. 0.1 Hz to 10 Hz RTI Voltage Noise (G = 1000) 00775-0-014 Figure 12. 0.1 Hz to 10 Hz Current Noise, 5 pA/Div 100 1000 AD620A FET INPUT IN-AMP SOURCE RESISTANCE (Ω) TOTAL DRIFT FROM 25°C TO 85°C, RTI (μV) 100,000 10 1k 10M 10,000 10k 100k 1M 00775-0-015 Figure 13. Total Drift vs. Source Resistance FREQUENCY (Hz) CMR (dB) 160 0 1M 80 40 1 60 0.1 140 100 120 10 100 1k 10k 100k G = 1000 G = 100 G = 10 G = 1 20 00775-0-016 Figure 14. Typical CMR vs. Frequency, RTI, Zero to 1 kΩ Source Imbalance AD620 Rev. H | Page 8 of 20 FREQUENCY (Hz) PSR (dB) 160 1M 80 40 1 60 0.1 140 100 120 10 100 1k 10k 100k 20 G = 1000 G = 100 G = 10 G = 1 180 00775-0-017 Figure 15. Positive PSR vs. Frequency, RTI (G = 1−1000) FREQUENCY (Hz) PSR (dB) 160 1M 80 40 1 60 0.1 140 100 120 10 100 1k 10k 100k 20 180 G = 10 G = 100 G = 1 G = 1000 00775-0-018 Figure 16. Negative PSR vs. Frequency, RTI (G = 1−1000) 1000 100 10M 100 1 1k 10 10k 100k 1M FREQUENCY (Hz) GAIN (V/V) 0.1 00775-0-019 Figure 17. Gain vs. Frequency OUTPUT VOLTAGE (V p-p) FREQUENCY (Hz) 35 0 1M 15 5 10k 10 1k 30 20 25 100k G = 10, 100, 1000 G = 1 G = 1000 G = 100 BW LIMIT 00775-0-020 Figure 18. Large Signal Frequency Response INPUT VOLTAGE LIMIT (V) (REFERRED TO SUPPLY VOLTAGES) 20 +1.0 +0.5 0 5 +1.5 –1.5 –1.0 –0.5 10 15 SUPPLY VOLTAGE ± Volts +VS –0.0 –VS +0.0 00775-0-021 Figure 19. Input Voltage Range vs. Supply Voltage, G = 1 20 +1.0 +0.5 0 5 +1.5 –1.5 –1.0 –0.5 10 15 SUPPLY VOLTAGE ± Volts RL = 10kΩ RL = 2kΩ RL = 10kΩ OUTPUT VOLTAGE SWING (V) (REFERRED TO SUPPLY VOLTAGES) RL = 2kΩ +VS –VS 00775-0-022 –0.0 +0.0 Figure 20. Output Voltage Swing vs. Supply Voltage, G = 10 AD620 Rev. H | Page 9 of 20 OUTPUT VOLTAGE SWING (V p-p) LOAD RESISTANCE (Ω) 30 0 0 10k 20 10 100 1k VS = ±15V G = 10 00775-0-023 Figure 21. Output Voltage Swing vs. Load Resistance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-024 Figure 22. Large Signal Pulse Response and Settling Time G = 1 (0.5 mV = 0.01%) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-025 Figure 23. Small Signal Response, G = 1, RL = 2 kΩ, CL = 100 pF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-026 Figure 24. Large Signal Response and Settling Time, G = 10 (0.5 mV = 0.01%) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-027 Figure 25. Small Signal Response, G = 10, RL = 2 kΩ, CL = 100 pF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-030 Figure 26. Large Signal Response and Settling Time, G = 100 (0.5 mV = 0.01%) AD620 Rev. H | Page 10 of 20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-029 Figure 27. Small Signal Pulse Response, G = 100, RL = 2 kΩ, CL = 100 pF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-030 Figure 28. Large Signal Response and Settling Time, G = 1000 (0.5 mV = 0.01% ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-031 Figure 29. Small Signal Pulse Response, G = 1000, RL = 2 kΩ, CL = 100 pF OUTPUT STEP SIZE (V) SETTLING TIME (μs) TO 0.01% TO 0.1% 20 0 0 2 15 5 5 10 10 15 0 00775-0-032 Figure 30. Settling Time vs. Step Size (G = 1) GAIN SETTLING TIME (μs) 1000 1 1 1000 100 10 10 100 00775-0-033 Figure 31. Settling Time to 0.01% vs. Gain, for a 10 V Step . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-034 Figure 32. Gain Nonlinearity, G = 1, RL = 10 kΩ (10 μV = 1 ppm) AD620 Rev. H | Page 11 of 20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-035 Figure 33. Gain Nonlinearity, G = 100, RL = 10 kΩ (100 μV = 10 ppm) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 00775-0-036 Figure 34. Gain Nonlinearity, G = 1000, RL = 10 kΩ (1 mV = 100 ppm) AD620 VOUT G = 1000 G = 1 49.9Ω 10kΩ * 1kΩ 10T 10kΩ 499Ω G = 100 G = 10 5.49kΩ +VS 11kΩ 1kΩ 100Ω 100kΩ INPUT 10V p-p –VS *ALL RESISTORS 1% TOLERANCE 1 7 2 3 8 6 4 5 00775-0-037 Figure 35. Settling Time Test Circuit AD620 Rev. H | Page 12 of 20 THEORY OF OPERATION VB –VS A1 A2 A3 C2 RG R1 R2 GAIN SENSE GAIN SENSE 10kΩ 10kΩ I1 I2 10kΩ REF 10kΩ +IN – IN R4 400Ω OUTPUT C1 Q1 Q2 00775-0-038 R3 400Ω +VS +VS +VS 20μA 20μA Figure 36. Simplified Schematic of AD620 The AD620 is a monolithic instrumentation amplifier based on a modification of the classic three op amp approach. Absolute value trimming allows the user to program gain accurately (to 0.15% at G = 100) with only one resistor. Monolithic construction and laser wafer trimming allow the tight matching and tracking of circuit components, thus ensuring the high level of performance inherent in this circuit. The input transistors Q1 and Q2 provide a single differentialpair bipolar input for high precision (Figure 36), yet offer 10× lower input bias current thanks to Superϐeta processing. Feedback through the Q1-A1-R1 loop and the Q2-A2-R2 loop maintains constant collector current of the input devices Q1 and Q2, thereby impressing the input voltage across the external gain setting resistor RG. This creates a differential gain from the inputs to the A1/A2 outputs given by G = (R1 + R2)/RG + 1. The unity-gain subtractor, A3, removes any common-mode signal, yielding a single-ended output referred to the REF pin potential. The value of RG also determines the transconductance of the preamp stage. As RG is reduced for larger gains, the transconductance increases asymptotically to that of the input transistors. This has three important advantages: (a) Open-loop gain is boosted for increasing programmed gain, thus reducing gain related errors. (b) The gain-bandwidth product (determined by C1 and C2 and the preamp transconductance) increases with programmed gain, thus optimizing frequency response. (c) The input voltage noise is reduced to a value of 9 nV/√Hz, determined mainly by the collector current and base resistance of the input devices. The internal gain resistors, R1 and R2, are trimmed to an absolute value of 24.7 kΩ, allowing the gain to be programmed accurately with a single external resistor. The gain equation is then 1 49.4 + Ω = RG k G 1 49.4 − Ω = G k RG Make vs. Buy: a Typical Bridge Application Error Budget The AD620 offers improved performance over “homebrew” three op amp IA designs, along with smaller size, fewer components, and 10× lower supply current. In the typical application, shown in Figure 37, a gain of 100 is required to amplify a bridge output of 20 mV full-scale over the industrial temperature range of −40°C to +85°C. Table 4 shows how to calculate the effect various error sources have on circuit accuracy. AD620 Rev. H | Page 13 of 20 Regardless of the system in which it is being used, the AD620 provides greater accuracy at low power and price. In simple systems, absolute accuracy and drift errors are by far the most significant contributors to error. In more complex systems with an intelligent processor, an autogain/autozero cycle removes all absolute accuracy and drift errors, leaving only the resolution errors of gain, nonlinearity, and noise, thus allowing full 14-bit accuracy. Note that for the homebrew circuit, the OP07 specifications for input voltage offset and noise have been multiplied by √2. This is because a three op amp type in-amp has two op amps at its inputs, both contributing to the overall input error. R = 350Ω 10V PRECISION BRIDGE TRANSDUCER R = 350Ω R = 350Ω R = 350Ω 00775-0-039 AD620A MONOLITHIC INSTRUMENTATION AMPLIFIER, G = 100 SUPPLY CURRENT = 1.3mA MAX AD620A RG 499Ω REFERENCE 00775-0-040 Figure 37. Make vs. Buy "HOMEBREW" IN-AMP, G = 100 *0.02% RESISTOR MATCH, 3ppm/°C TRACKING **DISCRETE 1% RESISTOR, 100ppm/°C TRACKING SUPPLY CURRENT = 15mA MAX 100Ω ** 10kΩ * 10kΩ ** 10kΩ * 10kΩ * 10kΩ ** 10kΩ* OP07D OP07D OP07D 00775-0-041 Table 4. Make vs. Buy Error Budget Error, ppm of Full Scale Error Source AD620 Circuit Calculation “Homebrew” Circuit Calculation AD620 Homebrew ABSOLUTE ACCURACY at TA = 25°C Input Offset Voltage, μV 125 μV/20 mV (150 μV × √2)/20 mV 6,250 10,607 Output Offset Voltage, μV 1000 μV/100 mV/20 mV ((150 μV × 2)/100)/20 mV 500 150 Input Offset Current, nA 2 nA ×350 Ω/20 mV (6 nA ×350 Ω)/20 mV 18 53 CMR, dB 110 dB(3.16 ppm) ×5 V/20 mV (0.02% Match × 5 V)/20 mV/100 791 500 Total Absolute Error 7,559 11,310 DRIFT TO 85°C Gain Drift, ppm/°C (50 ppm + 10 ppm) ×60°C 100 ppm/°C Track × 60°C 3,600 6,000 Input Offset Voltage Drift, μV/°C 1 μV/°C × 60°C/20 mV (2.5 μV/°C × √2 × 60°C)/20 mV 3,000 10,607 Output Offset Voltage Drift, μV/°C 15 μV/°C × 60°C/100 mV/20 mV (2.5 μV/°C × 2 × 60°C)/100 mV/20 mV 450 150 Total Drift Error 7,050 16,757 RESOLUTION Gain Nonlinearity, ppm of Full Scale 40 ppm 40 ppm 40 40 Typ 0.1 Hz to 10 Hz Voltage Noise, μV p-p 0.28 μV p-p/20 mV (0.38 μV p-p × √2)/20 mV 14 27 Total Resolution Error 54 67 Grand Total Error 14,663 28,134 G = 100, VS = ±15 V. (All errors are min/max and referred to input.) AD620 Rev. H | Page 14 of 20 3kΩ 5V DIGITAL DATA OUTPUT ADC REF IN AGND 20kΩ 10kΩ 20kΩ G = 100 AD620B 1.7mA 0.10mA 0.6mA MAX 499Ω 3kΩ 3kΩ 3kΩ 2 1 8 3 7 6 5 4 1.3mA MAX AD705 00775-0-042 Figure 38. A Pressure Monitor Circuit that Operates on a 5 V Single Supply Pressure Measurement Although useful in many bridge applications, such as weigh scales, the AD620 is especially suitable for higher resistance pressure sensors powered at lower voltages where small size and low power become more significant. Figure 38 shows a 3 kΩ pressure transducer bridge powered from 5 V. In such a circuit, the bridge consumes only 1.7 mA. Adding the AD620 and a buffered voltage divider allows the signal to be conditioned for only 3.8 mA of total supply current. Small size and low cost make the AD620 especially attractive for voltage output pressure transducers. Since it delivers low noise and drift, it also serves applications such as diagnostic noninvasive blood pressure measurement. Medical ECG The low current noise of the AD620 allows its use in ECG monitors (Figure 39) where high source resistances of 1 MΩ or higher are not uncommon. The AD620’s low power, low supply voltage requirements, and space-saving 8-lead mini-DIP and SOIC package offerings make it an excellent choice for batterypowered data recorders. Furthermore, the low bias currents and low current noise, coupled with the low voltage noise of the AD620, improve the dynamic range for better performance. The value of capacitor C1 is chosen to maintain stability of the right leg drive loop. Proper safeguards, such as isolation, must be added to this circuit to protect the patient from possible harm. G = 7 AD620A 0.03Hz HIGHPASS FILTER OUTPUT 1V/mV +3V –3V RG 8.25kΩ 24.9kΩ 24.9kΩ AD705J G = 143 C1 1MΩ R4 10kΩ R1 R3 R2 OUTPUT AMPLIFIER PATIENT/CIRCUIT PROTECTION/ISOLATION 00775-0-043 Figure 39. A Medical ECG Monitor Circuit AD620 Rev. H | Page 15 of 20 Precision V-I Converter The AD620, along with another op amp and two resistors, makes a precision current source (Figure 40). The op amp buffers the reference terminal to maintain good CMR. The output voltage, VX, of the AD620 appears across R1, which converts it to a current. This current, less only the input bias current of the op amp, then flows out to the load. RG AD620 –VS VIN+ VIN– LOAD R1 IL Vx I L = R1 = IN+ [(V ) – (V IN – )] G R1 6 5 + VX – 2 4 1 8 3 7 +VS AD705 00775-0-044 Figure 40. Precision Voltage-to-Current Converter (Operates on 1.8 mA, ±3 V) GAIN SELECTION The AD620 gain is resistor-programmed by RG, or more precisely, by whatever impedance appears between Pins 1 and 8. The AD620 is designed to offer accurate gains using 0.1% to 1% resistors. Table 5 shows required values of RG for various gains. Note that for G = 1, the RG pins are unconnected (RG = ∞). For any arbitrary gain, RG can be calculated by using the formula: 1 49.4 − Ω = G k RG To minimize gain error, avoid high parasitic resistance in series with RG; to minimize gain drift, RG should have a low TC—less than 10 ppm/°C—for the best performance. Table 5. Required Values of Gain Resistors 1% Std Table Value of RG(Ω) Calculated Gain 0.1% Std Table Value of RG(Ω ) Calculated Gain 49.9 k 1.990 49.3 k 2.002 12.4 k 4.984 12.4 k 4.984 5.49 k 9.998 5.49 k 9.998 2.61 k 19.93 2.61 k 19.93 1.00 k 50.40 1.01 k 49.91 499 100.0 499 100.0 249 199.4 249 199.4 100 495.0 98.8 501.0 49.9 991.0 49.3 1,003.0 INPUT AND OUTPUT OFFSET VOLTAGE The low errors of the AD620 are attributed to two sources, input and output errors. The output error is divided by G when referred to the input. In practice, the input errors dominate at high gains, and the output errors dominate at low gains. The total VOS for a given gain is calculated as Total Error RTI = input error + (output error/G) Total Error RTO = (input error × G) + output error REFERENCE TERMINAL The reference terminal potential defines the zero output voltage and is especially useful when the load does not share a precise ground with the rest of the system. It provides a direct means of injecting a precise offset to the output, with an allowable range of 2 V within the supply voltages. Parasitic resistance should be kept to a minimum for optimum CMR. INPUT PROTECTION The AD620 safely withstands an input current of ±60 mA for several hours at room temperature. This is true for all gains and power on and off, which is useful if the signal source and amplifier are powered separately. For longer time periods, the input current should not exceed 6 mA. For input voltages beyond the supplies, a protection resistor should be placed in series with each input to limit the current to 6 mA. These can be the same resistors as those used in the RFI filter. High values of resistance can impact the noise and AC CMRR performance of the system. Low leakage diodes (such as the BAV199) can be placed at the inputs to reduce the required protection resistance. AD620 R REF R +SUPPLY –SUPPLY VOUT +IN –IN 00775-0-052 Figure 41. Diode Protection for Voltages Beyond Supply RF INTERFERENCE All instrumentation amplifiers rectify small out of band signals. The disturbance may appear as a small dc voltage offset. High frequency signals can be filtered with a low pass R-C network placed at the input of the instrumentation amplifier. Figure 42 demonstrates such a configuration. The filter limits the input AD620 Rev. H | Page 16 of 20 signal according to the following relationship: 2 (2 ) 1 D C DIFF R C C FilterFreq π + = C CM RC FilterFreq π = 2 1 where CD ≥10CC. CD affects the difference signal. CC affects the common-mode signal. Any mismatch in R × CC degrades the AD620 CMRR. To avoid inadvertently reducing CMRR-bandwidth performance, make sure that CC is at least one magnitude smaller than CD. The effect of mismatched CCs is reduced with a larger CD:CC ratio. 499Ω AD620 + – VOUT R R CC CD CC +IN –IN REF –15V 0.1μ F 10μ F +15V 0.1μ F 10μ F 00775-0-045 Figure 42. Circuit to Attenuate RF Interference COMMON-MODE REJECTION Instrumentation amplifiers, such as the AD620, offer high CMR, which is a measure of the change in output voltage when both inputs are changed by equal amounts. These specifications are usually given for a full-range input voltage change and a specified source imbalance. For optimal CMR, the reference terminal should be tied to a low impedance point, and differences in capacitance and resistance should be kept to a minimum between the two inputs. In many applications, shielded cables are used to minimize noise; for best CMR over frequency, the shield should be properly driven. Figure 43 and Figure 44 show active data guards that are configured to improve ac common-mode rejections by “bootstrapping” the capacitances of input cable shields, thus minimizing the capacitance mismatch between the inputs. REFERENCE VOUT AD620 100Ω 100Ω – INPUT + INPUT AD648 RG –VS +VS –VS 00775-0-046 Figure 43. Differential Shield Driver 100Ω – INPUT + INPUT REFERENCE VOUT AD620 –VS +VS 2 RG 2 RG AD548 00775-0-047 Figure 44. Common-Mode Shield Driver GROUNDING Since the AD620 output voltage is developed with respect to the potential on the reference terminal, it can solve many grounding problems by simply tying the REF pin to the appropriate “local ground.” To isolate low level analog signals from a noisy digital environment, many data-acquisition components have separate analog and digital ground pins (Figure 45). It would be convenient to use a single ground line; however, current through ground wires and PC runs of the circuit card can cause hundreds of millivolts of error. Therefore, separate ground returns should be provided to minimize the current flow from the sensitive points to the system ground. These ground returns must be tied together at some point, usually best at the ADC package shown in Figure 45. DIGITAL P.S. C +5V ANALOG P.S. +15V C –15V AD574A DIGITAL DATA OUTPUT + 1μF AD620 0.1μF AD585 S/H ADC 0.1μF 1μF 1μF 00775-0-048 Figure 45. Basic Grounding Practice AD620 Rev. H | Page 17 of 20 GROUND RETURNS FOR INPUT BIAS CURRENTS VOUT – INPUT + INPUT RG LOAD TO POWER SUPPLY GROUND REFERENCE +VS –VS AD620 00775-0-050 Input bias currents are those currents necessary to bias the input transistors of an amplifier. There must be a direct return path for these currents. Therefore, when amplifying “floating” input sources, such as transformers or ac-coupled sources, there must be a dc path from each input to ground, as shown in Figure 46, Figure 47, and Figure 48. Refer to A Designer’s Guide to Instrumentation Amplifiers (free from Analog Devices) for more information regarding in-amp applications. AD620 VOUT – INPUT RG TO POWER SUPPLY GROUND + INPUT REFERENCE +VS –VS LOAD 00775-0-049 Figure 47. Ground Returns for Bias Currents with Thermocouple Inputs 100kΩ AD620 VOUT – INPUT + INPUT RG LOAD TO POWER SUPPLY GROUND REFERENCE 100kΩ –VS +VS 00775-0-051 Figure 46. Ground Returns for Bias Currents with Transformer-Coupled Inputs Figure 48. Ground Returns for Bias Currents with AC-Coupled Inputs AD620 Rev. H | Page 18 of 20 AD620ACHIPS INFORMATION Die size: 1803 μm × 3175 μm Die thickness: 483 μm Bond Pad Metal: 1% Copper Doped Aluminum To minimize gain errors introduced by the bond wires, use Kelvin connections between the chip and the gain resistor, RG, by connecting Pad 1A and Pad 1B in parallel to one end of RG and Pad 8A and Pad 8B in parallel to the other end of RG. For unity gain applications where RG is not required, Pad 1A and Pad 1B must be bonded together as well as the Pad 8A and Pad 8B. 1A 1B 2 3 4 5 6 7 8A 8B LOGO 00775-0-053 Figure 49. Bond Pad Diagram Table 6. Bond Pad Information Pad Coordinates1 Pad No. Mnemonic X (μm) Y (μm) 1A RG −623 +1424 1B RG −789 +628 2 −IN −790 +453 3 +IN −790 −294 4 −VS −788 −1419 5 REF +570 −1429 6 OUTPUT +693 −1254 7 +VS +693 +139 8A RG +505 +1423 8B RG +693 +372 1 The pad coordinates indicate the center of each pad, referenced to the center of the die. The die orientation is indicated by the logo, as shown in Figure 49. AD620 Rev. H | Page 19 of 20 OUTLINE DIMENSIONS COMPLIANT TO JEDEC STANDARDS MS-001 CONTROLLING DIMENSIONS ARE IN INCHES; MILLIMETER DIMENSIONS (IN PARENTHESES) ARE ROUNDED-OFF INCH EQUIVALENTS FOR REFERENCE ONLY AND ARE NOT APPROPRIATE FOR USE IN DESIGN. CORNER LEADS MAY BE CONFIGURED AS WHOLE OR HALF LEADS. 070606-A 0.022 (0.56) 0.018 (0.46) 0.014 (0.36) SEATING PLANE 0.015 (0.38) MIN 0.210 (5.33) MAX 0.150 (3.81) 0.130 (3.30) 0.115 (2.92) 0.070 (1.78) 0.060 (1.52) 0.045 (1.14) 8 1 4 5 0.280 (7.11) 0.250 (6.35) 0.240 (6.10) 0.100 (2.54) BSC 0.400 (10.16) 0.365 (9.27) 0.355 (9.02) 0.060 (1.52) MAX 0.430 (10.92) MAX 0.014 (0.36) 0.010 (0.25) 0.008 (0.20) 0.325 (8.26) 0.310 (7.87) 0.300 (7.62) 0.195 (4.95) 0.130 (3.30) 0.115 (2.92) 0.015 (0.38) GAUGE PLANE 0.005 (0.13) MIN Figure 50. 8-Lead Plastic Dual In-Line Package [PDIP] Narrow Body (N-8). Dimensions shown in inches and (millimeters) CONTROLLING DIMENSIONS ARE IN INCHES; MILLIMETER DIMENSIONS (IN PARENTHESES) ARE ROUNDED-OFF INCH EQUIVALENTS FOR REFERENCE ONLY AND ARE NOT APPROPRIATE FOR USE IN DESIGN. 0.310 (7.87) 0.220 (5.59) 0.005 (0.13) MIN 0.055 (1.40) MAX 0.100 (2.54) BSC 15° 0° 0.320 (8.13) 0.290 (7.37) 0.015 (0.38) SEATING 0.008 (0.20) PLANE 0.200 (5.08) MAX 0.405 (10.29) MAX 0.150 (3.81) MIN 0.200 (5.08) 0.125 (3.18) 0.023 (0.58) 0.014 (0.36) 0.070 (1.78) 0.030 (0.76) 0.060 (1.52) 0.015 (0.38) 1 4 8 5 Figure 51. 8-Lead Ceramic Dual In-Line Package [CERDIP] (Q-8) Dimensions shown in inches and (millimeters) CONTROLLING DIMENSIONS ARE IN MILLIMETERS; INCH DIMENSIONS (IN PARENTHESES) ARE ROUNDED-OFF MILLIMETER EQUIVALENTS FOR REFERENCE ONLY AND ARE NOT APPROPRIATE FOR USE IN DESIGN. COMPLIANT TO JEDEC STANDARDS MS-012-AA 012407-A 0.25 (0.0098) 0.17 (0.0067) 1.27 (0.0500) 0.40 (0.0157) 0.50 (0.0196) 0.25 (0.0099) 45° 8° 0° 1.75 (0.0688) 1.35 (0.0532) SEATING PLANE 0.25 (0.0098) 0.10 (0.0040) 1 4 8 5 5.00 (0.1968) 4.80 (0.1890) 4.00 (0.1574) 3.80 (0.1497) 1.27 (0.0500) BSC 6.20 (0.2441) 5.80 (0.2284) 0.51 (0.0201) 0.31 (0.0122) COPLANARITY 0.10 Figure 52. 8-Lead Standard Small Outline Package [SOIC_N] Narrow Body (R-8) Dimensions shown in millimeters and (inches) AD620 Rev. H | Page 20 of 20 ORDERING GUIDE Model1 Temperature Range Package Description Package Option AD620AN −40°C to +85°C 8-Lead PDIP N-8 AD620ANZ −40°C to +85°C 8-Lead PDIP N-8 AD620BN −40°C to +85°C 8-Lead PDIP N-8 AD620BNZ −40°C to +85°C 8-Lead PDIP N-8 AD620AR −40°C to +85°C 8-Lead SOIC_N R-8 AD620ARZ −40°C to +85°C 8-Lead SOIC_N R-8 AD620AR-REEL −40°C to +85°C 8-Lead SOIC_N, 13" Tape and Reel R-8 AD620ARZ-REEL −40°C to +85°C 8-Lead SOIC_N, 13" Tape and Reel R-8 AD620AR-REEL7 −40°C to +85°C 8-Lead SOIC_N, 7" Tape and Reel R-8 AD620ARZ-REEL7 −40°C to +85°C 8-Lead SOIC_N, 7" Tape and Reel R-8 AD620BR −40°C to +85°C 8-Lead SOIC_N R-8 AD620BRZ −40°C to +85°C 8-Lead SOIC_N R-8 AD620BR-REEL −40°C to +85°C 8-Lead SOIC_N, 13" Tape and Reel R-8 AD620BRZ-RL −40°C to +85°C 8-Lead SOIC_N, 13" Tape and Reel R-8 AD620BR-REEL7 −40°C to +85°C 8-Lead SOIC_N, 7" Tape and Reel R-8 AD620BRZ-R7 −40°C to +85°C 8-Lead SOIC_N, 7" Tape and Reel R-8 AD620ACHIPS −40°C to +85°C Die Form AD620SQ/883B −55°C to +125°C 8-Lead CERDIP Q-8 1 Z = RoHS Compliant Part. © 2003–2011 Analog Devices, Inc. All rights reserved. Trademarks and registered trademarks are the property of their respective owners. C00775–0–7/11(H) a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 1 of 18 a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 2 of 18 Table of Contents SUMMARY...................................................................................................................... 3 1 DEAD TIME EFFECTS AND THEIR COMPENSATION .......................................... 3 2 IMPLEMENTATION OF THE FEED FORWARD DEAD TIME COMPENSATION .. 5 2.1 Using the dt_comp routines ...........................................................................................................................5 2.2 Using the dt_comp routine.............................................................................................................................6 2.3 The program code...........................................................................................................................................7 3 EXAMPLE: TESTING THE VALIDITY OF THE FEED FORWARD DEAD TIME COMPENSATION........................................................................................................... 7 3.1 The construction of an inverter .....................................................................................................................7 3.2 The software program used to test the feed forward dead time compensation.........................................8 3.3 The main include file: main.h ......................................................................................................................12 3.4 The program offset.dsp and its header offset.h..........................................................................................12 3.5 Experimental results.....................................................................................................................................16 4 REFERENCES ....................................................................................................... 18 a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 3 of 18 Summary Due to the finite switching time, in order to prevent the appearance of short circuits, the power devices of an inverter must be commanded introducing a delay between their active times. This delay, called dead time because in this period no power device is active, introduces small voltage errors, which are sufficient to produce distorted motor currents, oscillations of the motor torque and therefore even the motor controllability may be lost [1]. This paper presents one method to compensate the effects of the dead time, the experimental hardware on which this method was tested and the assembly program associated with it. 1 Dead Time effects and their compensation Consider a voltage inverter with a motor connected at its output terminals (Figure 1). d V T1 T 2 T 3 T 4 T 5 T 6 D1 D2 D4 D6 D3 D5 0 1 ≥ s i 1 v Figure 1: Voltage source inverter The effects of the dead time may be examined by considering only the first phase of the inverter. On this phase it is desired to obtain the reference PWM signal * 1 v presented in Figure 2a. The signals used to command the power devices are assumed to be active LOW, which means that when they are LOW, the power devices conduct (Figures 2b and 2c). The output signal obtained at the motor terminal depends on the sense of the current flowing in this phase: In the case of the current flowing from inverter to the motor (assumed positive sense), when T2 conducts, the phase terminal is linked to the GND and the voltage 1 v is 0. During the dead time period, when both power devices are turned OFF, the current continues to flow into the motor using the reverse recovery diode D2, so 1 v will continue to be 0. When the upper power device T1 conducts, the phase terminal is connected to d V and 1 v is equal to d V . During the second half cycle, the phenomenon repeats itself a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 4 of 18 symmetrically. The final behaviour of 1 v is presented in Figure 2d. It may be observed that the average value of 1 v is less than the reference value by an amount determined by the dead time: d s V T DT v = v − ⋅ * 1 1 (1) DT DT 2 DT 2 s T * 1 v T1 T 2 0 1 1 ≥ s i v when 0 1 1 < s i v when a) b) c) d) e) d V d V * 1 T Figure 2: The influence of the dead time over the output phase voltage In the case of the current flowing from the motor to the inverter, when T2 conducts, the phase terminal is linked to the GND and the voltage 1 v is 0. During the dead time period, the current continues to flow from the motor using the reverse recovery diode D1, so 1 v will become equal to d V . When the upper power device T1 conducts, the phase terminal is connected to d V and 1 v will continue to be equal to d V . During the second half, the phenomenon repeats itself symmetrically. The final behaviour of 1 v is presented in Figure 2e. It may be observed that the average value of 1 v is greater than the reference value by an amount determined by the dead time: a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 5 of 18 d s V T DT v = v + ⋅ * 1 1 (2) Equations (1) and (2) provide the first method to compensate for the dead time: the feed-forward compensation. In relation of the current sense, the inverter phase will be commanded with a reference voltage ** 1 v such that the voltage 1 v at the inverter terminal will become equal with the reference voltage * 1 v : d s V T DT v = v + ⋅ * 1 ** 1 when i ≥ 0 (3) d s V T DT v = v − ⋅ * 1 ** 1 when i < 0 . These expressions mean that when the phase current is positive, the duty cycle * 1 T correspondent to * 1 v has to be increased by the dead time and when the phase current is negative, the duty cycle has to be decreased by the dead time. The only drawback of this method appears when the current changes its sign, because this moment cannot be foreseen. It is easily seen that when the sign is not correctly applied, an error of two times the dead time is introduced. Another method to compensate the dead time is the following: The actual inverter voltages are measured on every phase. The compensation is done adding to the reference phase voltage * 1 v a term proportional to the voltage error on that phase: ( 1) ( 1) [ ( ) ( )] 1 * 1 * 1 ** 1 v k + = v k + + K ⋅ v k − v k (4) where: - ** ( 1) 1 v k + is the voltage which will be commanded on the first inverter phase; - * ( 1) 1 v k + is the reference voltage which would have been commanded if the dead time compensation had not been considered; -K is the gain of the compensator, usually less than or equal to 1; - * ( ) 1 v k is the reference voltage which would have been commanded during the previous PWM cycle if the dead time compensation had not been considered; - ( ) 1 v k is the inverter phase voltage measured during the previous PWM cycle. The drawback of this method is that all the inverter phase voltages have to be measured. It is possible to measure only two inverter phases if the PWM modulation is space vector type or sinusoidal. 2 Implementation of the feed forward dead time compensation 2.1 Using the dt_comp routines The routines are developed as an easy-to-use library, which has to be linked to the user’s application. The library consists of two files. The file “dt_comp.dsp” contains the assembly code of the subroutines. The block has to be compiled and then linked to an application. The user has to include the header file dt_comp.h, which provides the function-like calls to the subroutines. The example file in Section 3 will demonstrate the usage of all the routines. a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 6 of 18 Operation Usage Compute On-times compensating the dead time DeadTime_Comp(StatorCurrent_struct, Dutycycles_struct) Table 1 Implemented routine The input vector StatorCurrents_struct consists of three elements, the three inverter phase currents. Because their sum is always zero, only two of them need to be measured. They have to be scaled because the DSP uses fixed point formats. The scaling factor is 2⋅ Imax , where max I represents the maximum current which may be placed at the input pin of the A/D converter. The 2 factor is used to prevent overflows when the currents are used in arithmetical operations. The vector Dutycycles_struct is an input and also an output: It represents the duty cycles for each phase, previously computed by the PWM modulator. After the compensation, they represent the duty cycles effectively commanded to the inverter. Their values have to be between 0 and PWMTM, the number which controls the PWM switching frequency. DeadTime_comp represents a macro, which must be introduced into the program code if the dead time compensation is desired. The format of inputs and outputs are explained in more detail in the next section. The routines do not require any configuration constants from the main include-file “main.h” that comes with every application note. For more information about the general structure of the application notes and including libraries into user applications refer to the Library Documentation File. Section 2.2 shows an example of usage of this library. In the following sections each routine is explained in detail with the relevant segments of code which is found in either “dt_comp.h” or “dt_comp.dsp”. For more information see the comments in those files. 2.2 Using the dt_comp routine The macro listed in the Table 1 is based on a subroutine called DeadTime_Comp_. It is described in detail in the next section. The following table gives an overview of what DSP registers are used in this macro: Macro Input1 and modified DAG registers Output2 Modified core registers DeadTime_Comp I1 = ^ StatorCurrents_struct; M1, M2 = 1; L1, L2 = 0; I2 = ^ Dutycycles_struct; M3 = 0; N/A AX0, AY0, AY1, MR, AR Table2. DSP core registers used in the macro This macro has to be placed in the main program after the PWM reference duty cycles are computed, but prior to the program that saves them into the duty cycle registers PWMCHA, PWMCHB, PWMCHC. 1 ^vector stands for ‘address of vector’. 2 N/A: The output values are stored in the output vector in the Data memory. No DSP core register is used. a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 7 of 18 2.3 The program code The following code contained in the file dt_comp.dsp describes the routine DeadTime_Comp_ mentioned in the previous section. The routine is organised as a loop managed by the loop counter cntr. At each iteration, one phase current from the buffer StatorCurrents_struct is tested and the compensation is done function of its sign. In the end, the new duty cycle number is tested to ensure it is positive and less than the maximum admissible value, PWMTM. The last instruction saves the number back into the buffer Dutycycles_struct. DeadTime_Comp_: AY0 = DM(PWMDT); { dead time normalized } AY1 = dm(PWMTM); CNTR = 3; do dead_loop until ce; ax0 = DM(I1, M1); { ax0 = Isk, k=1,2,3 } mr1 = DM(I2, M3); { load Ta, Tb, Tc } AR = MR1 + AY0; none = pass ax0; {chek sign of the currents } IF LT AR = MR1 - AY0; none = pass AR; if lt AR = PASS 0; { no negative values admitted} af = AR - AY1; if gt ar = pass ay1; { protection against overflows} dead_loop: DM(I2, M2) = ar; rts; 3 Example: Testing the validity of the feed forward dead time compensation 3.1 The construction of the inverter The proposed compensation method was implemented on the ADMC331 Processor Board mounted on an ADMC Connector Board. As inverter power part was used an evaluation platform produced by International Rectifier, IRPT2056D Driver-Plus Board. It is a three phase 230VAC 3HP board and it integrates all the processing components needed for a 3 HP motor drive. It is equipped with an IRPT2056A IGBT power module and an IR2133J driver. The Analog Devices’ ADMC PWM isolation board linked the Connector Board to the Power Board. This board produces an electric isolation between the digital part and the inverter power part and also inverts the signals used to drive the power devices (74HC240). Because the signals used by the driver IR2133J are active LOW and because of the inverting line driver HC240, the PWM outputs of the ADMC331 are set to be active HIGH. Therefore the jumper JP51 is in position 1-2. 1 See the ADMC331 Processor Board manual, Motion Control Group, Analog Devices, 1998 a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 8 of 18 The inverter is driving an induction motor with the following characteristics: .13HP, 230V, 60Hz, 1725rpm, produced by Baldor. Because the power part is supplied with 110V, the maximum frequency the motor may be run in the constant torque regime is: c f 3 110 2 60 3 230 2 ⋅ = ⋅ f Hz c 28.7 230 110 60 = ⋅ = Because the compensation needs the value of the inverter phase currents, two of them were sensed using current transducers HA 10-NP produced by LEM. They are capable to measure up to 20A and this value is used to scale down the measured values: I 20A max = . Also, an operational amplifier LM348 is used to obtain the signal into the range of A/D converter of ADMC331: 0.3V÷3.5V. On the ADMC331 Processor Board there are 5KHz filters that have an anti-aliasing role. A block structure of the inverter is presented in Figure 3. ADMCConnector Board ADMC331 Processor Board IRPT2056D Driver Plus Board ADMC PWM Isolation Board .13HP Induction Motor 2xHP10-NP s1 I s 2 I Figure 3. Inverter Block structure 3.2 The software program used to test the feed forward dead time compensation The purpose of this program is to demonstrate the improvement offered by the feed forward dead time compensation. It reads two motor currents, commands the motor to run at 14Hz, half of the cut frequency c f and compensates for the dead time. The file main.dsp contains the root program. The batch file build.bat compiles every file of the project, links them together and builds the executable file main.exe. It may be applied either within DOS prompt or clicking on it from Windows Explorer. Main.exe may be run on the Motion Control Debugger. A brief description of the program will be given in the following: Start of code - declaring start location in program memory .MODULE/RAM/SEG=USER_PM1/ABS=0x30 Main_Program; a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 9 of 18 Next, the general systems constants and PWM configuration constants (main.h – see the next section) are included. Also included are the PWM library2, the DAC interface3 and the space vector modulation4 module definitions. The header file offset.h declares some macros used to measure the offset introduced by the current transducers and autocal.h declares the macros used to calibrate the ADMC331 A/D converter. {*************************************************************************************** * Include General System Parameters and Libraries * ***************************************************************************************} #include ; #include ; #include ; #include ; #include ; #include ; #include ; #include ; #include ; { Application Specific Module } #include ; #include ; #include ; Constants used in this program {*************************************************************************************** * Constants Defined in the Module * ***************************************************************************************} .CONST CUT_FREQ = 28; {the cutting frequency of the tested motor} .CONST Delta = 32768*2*CUT_FREQ/PWM_freq; {the increment of the angle} .CONST TwoPiOverThree = 0xffff / 3; { Hex equivalent of 2pi/3 } .CONST ALLOFF = 0x3F; { Used to disable IGBTies into PWMSEG } Here is where all the vectors for the program are declared. The buffer StatorCurrents_struct represents the three stator currents. The PWM duty cycles are stored in the buffer Dutycycles_struct and they are initialised with 0.It may be seen that the variables which identify the current offsets, Is1Offset and Is2Offset are declared circular because programming becomes easier. The average of the readings is computed on 32bit precision, so every buffer consists of 2 words. {*************************************************************************************** * Local Variables Defined in this Module * ***************************************************************************************} .VAR/DM/RAM/SEG=USER_DM AD_IN; { Volts/Hertz Command (0-1) } 2 see AN331-03: Three-Phase Sine-Wave Generation using the PWM Unit of the ADMC331 3 see AN331-06: Using the Serial Digital to Analog Converter of the ADMC Connector Board 4 see AN331-17: Implementing Space Vector Modulation with the ADMC331 a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 10 of 18 .INIT AD_IN : 0x3A0A; { Corresponds to 0.906/2 } .VAR/DM/RAM/SEG=USER_DM Theta; { Current angle } .INIT Theta : 0x0000; .VAR/DM/RAM/SEG=USER_DM Vdq_ref[2]; { rotor ref.frame } .VAR/DM/RAM/CIRC/SEG=USER_DM Valphabeta_ref[2]; { alphabeta frame } .VAR/RAM/DM/SEG=USER_DM OnTime_struct[1*4]; .INIT OnTime_struct: 0x0000, 0x0000, 0x0000, 0x0000; .VAR/RAM/DM/SEG=USER_DM Dutycycles_struct[1*3]; .INIT Dutycycles_struct: 0x0000, 0x0000, 0x0000; .VAR/DM/RAM/SEG=USER_DM VrefA; { Voltage demands } .VAR/DM/RAM/SEG=USER_DM VrefB; .VAR/DM/RAM/SEG=USER_DM VrefC; .INIT VrefA : 0x0000; .INIT VrefB : 0x0000; .INIT VrefC : 0x0000; .VAR/DM/RAM/SEG=USER_DM StatorCurrents_struct[1*3]; { stator currents } .VAR/DM/RAM/SEG=USER_DM Is1Offset[1]; .VAR/DM/RAM/SEG=USER_DM Is2Offset[1]; When the program begins, the PWM output signals are disabled. Then, the power module is reset and the PWM block is set up to generate interrupts every 100μsec (see main.h in the next section). There is initialised the D/A serial converter1 and there is unmasked the IRQ2 interrupt (the interrupt which manages the peripheral interrupts on ADMC331). The main loop just waits for interrupts. {********************************************************************************************} { Start of program code } {********************************************************************************************} Startup: Write_DM(PWMSEG, ALLOFF); { the IGBTies are disabled } IR_reset_PIO3; { Reset PowIRTrain Module } PWM_Init(PWMSYNC_ISR, PWMTRIP_ISR); DAC_Init; { Initialize the DAC-Module } IFC = 0x80; { Clear any pending IRQ2 inter. } ay0 = 0x200; { unmask irq2 interrupts. } ar = IMASK; ar = ar or ay0; 1 See ADMC Connector board user’s manual for further details a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 11 of 18 IMASK = ar; { IRQ2 ints fully enabled here } ADC_Init; { ADC Counter will Operate at the DSP CLKOUT Frequency } AutoCal_Init; { Initialize the Auto Calibration Routine } Offset_Init; { offset.h } Main: { Wait for interrupt to occur } jump Main; During the PWM_SYNC interrupt there are executed some routines which determine the internal offset of the A/D converter1, the external offsets introduced by the current transducers and the measurement of the currents. The successive routines generate three PWM signals of 14Hz obtained applying a continuous space vector modulation2. The dead time compensation is placed at the end of this block. Finally, the signals that will be provided to the D/A converter are computed. {********************************************************************************************} { PWM Interrupt Service Routine } {********************************************************************************************} PWMSYNC_ISR: Auto_Calibrate; { autocal.h } OffsetDetermination(ADC1, ADC2, Is1Offset, Is2Offset); { offset.h } ReadCurrents(Is1Offset, Is2Offset, StatorCurrents_struct, ADC1, ADC2); { offset.h } DAC_Pause; { Required only when I1, M1 or L1 is used} ar = DM (AD_IN ); mr = 0; {Clear mr } mr1 = dm(Theta); {Preload Theta} my0 = Delta; mr = mr + ar*my0 (SS); {Compute new angle & store} dm(Theta) = mr1; DM(Vdq_ref )= ar; {Set constant Vdq reference (AD_IN,0)} ar = pass 0; DM(Vdq_ref+1)= ar; refframe_Set_DAG_registers_for_transformations; refframe_Forward_Park_angle(Vdq_ref,Valphabeta_ref,mr1); {generate Vreference in alpha-beta frame} SVPWM_Calc_Ontimes(Valphabeta_ref, OnTime_struct); { use SVPWM routines} SVPWM_Calc_Dutycycles(OnTime_struct, Dutycycles_struct); DeadTime_Comp(StatorCurrents_struct, Dutycycles_struct); SVPWM_Update_DutyCycles(Dutycycles_struct); Dac_Resume; my0 = DM(Theta); DAC_Put(1, my0); { output on DACs, amplified by multiplication } mx0 = 0x8; my0 = DM(Dutycycles_struct ); mr = mx0 * my0 (SS); Dac_Put(2, mr0); my0 = DM(Dutycycles_struct+1); mr = mx0 * my0 (SS); Dac_Put(3, mr0); 1 See AN331-05: ADC-system on the ADMC331. 2 See AN331-17: Implementing Space Vector Modulation with ADMC331 a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 12 of 18 AX0 = dm(Dutycycles_struct); AY0 = Half_PWMTM; AR = AX0 - AY0; MY0 = 0x6523; {2/PWMTM=2/1296*2^15/2^6*2^15} MR = AR * MY0 (SS); SR = ASHIFT MR1 BY 6 (HI); SR = SR OR LSHIFT MR0 BY 6 (LO); DAC_Put(4, SR1); sr1 = DM(StatorCurrents_struct); sr = ASHIFT sr1 BY 5 (HI); DAC_Put(5,sr1); sr1 = DM(StatorCurrents_struct+1); sr = ASHIFT sr1 BY 5 (HI); DAC_Put(6, sr1); SR1 = DM(StatorCurrents_struct+2); sr = ASHIFT sr1 BY 5 (HI); DAC_Put(7, sr1); DAC_Update; RTI; 3.3 The main include file: main.h This file contains the definitions of ADMC331 constants, general-purpose macros, the configuration parameters of the system and library routines. It should be included in every application. For more information refer to the Library Documentation File. This file is mostly self-explaining. As already mentioned, the dt_comp library does not require any configuration parameters. The following table presents the parameters used to initialise the PWM block .It may be emphasized the dead time period set at 6μsec, a large value for the power devices used on the IRPT2056D. {********************************************************************************************} { Library: PWM block } { file : PWM331.dsp } { Application Note: Usage of the ADMC331 Pulse Width Modulation Block } .CONST PWM_freq = 10000; {Desired PWM switching frequency [Hz] } .CONST PWM_deadtime = 6000; {Desired deadtime [nsec] } .CONST PWM_minpulse = 1000; {Desired minimal pulse time [nsec] } .CONST PWM_syncpulse = 1540; {Desired sync pulse time [nsec] } .CONST Half_PWMTM = 1000*Cry_clock/PWM_freq/2; {********************************************************************************************} 3.4 The program offset.dsp and its header offset.h The current transducers introduce an offset that has to be evaluated, otherwise the sign of the currents would be determined with large errors. For this reason, at the beginning of the program, for a certain number of PWM cycles (in this particular case 128, but may be more or less depending on the system) there are measured the A/D channels corresponding to the two phase currents, V1 and V2. The average of all measurements constitutes the offset of that current. Of course, this procedure may be applied at every channel, if the signal is zero at the beginning of the program. The header file offset.h contains the macros that are used during this process. Generally, they call subroutines presented in the file offset.dsp. This file begins declaring the variables OffsetCounter, TempOffset1 and TempOffset2 used in these routines. a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 13 of 18 {*************************************************************************************** * Global Variables Defined in this Module * ***************************************************************************************} .VAR/DM/RAM/SEG=USER_DM OffsetCounter[1]; .GLOBAL OffsetCounter; .VAR/DM/RAM/CIRC/SEG=USER_DM TempOffset1[2]; .GLOBAL TempOffset1; .VAR/DM/RAM/CIRC/SEG=USER_DM TempOffset2[2]; .GLOBAL TempOffset2; The subroutine Offset_Init_ initialises the variables used to evaluate the offsets of the current transducers. OffsetCounter is set to 128 because the offsets are considered the average of 128 measurements. {************************************************************************************* * Type: Routine * * Call: Call Offset_Init_; * * This subroutine initializes the variables initializes variables used to * * evaluate the offsets of the current sensors * * Inputs : None * * Ouputs :None * * Modified: AR * ***************************************************************************************} Offset_Init_: AR = Offset_Average; dm(OffsetCounter) = AR; AR = 0x0; dm(TempOffset1) = AR; dm(TempOffset1+1) = AR; dm(TempOffset2) = AR; dm(TempOffset2+1) = AR; rts; The subroutine EvaluateIs_offset_ computes the average of the measurements of a particular A/D channel. {*************************************************************************************** * Type: Routine * * Call: Call EvaluateIs_offset_; * * This subroutine computes the average of the measurements of one A/D channel * * Inputs : AR = the lecture of the A/D channel * I1 = placed at the begining of the buffer which is averaged * * M1 = 0, L1 = 0 * * Ouputs :None * * Modified: AY1, AY0, AR, SR, AX0 * ***************************************************************************************} EvaluateIs_offset_: AY1 = dm(I1, M1); a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 14 of 18 AY0 = dm(I1, M1); AR = 0x4000 - AR; SR = ASHIFT AR BY -7 (HI); AR = SR0 + AY0; AX0 = AR, AR = SR1 + AY1 + C; dm(I1, M1) = AR; dm(I1, M1) = AX0; RTS; . In the file offset.h there is a macro Offset_Init that initialises the address generators at the current offsets buffers and then calls the subroutine Offset_Init_ from offset.dsp. {*************************************************************************************** * Type: Macro * * Call: Offset_Init; * * This macro initializes variables used to evaluate the offsets of the current sensors * * Input: none * * Output: none * * Modified: AR * ***************************************************************************************} .MACRO Offset_Init; CALL Offset_Init_; .ENDMACRO; The macro EvaluateIs_offset reads one A/D channel and computes the average offset of that channel calling the subroutine EvaluateIs_offset_. {*************************************************************************************** * Type: Macro * * Call: EvaluateIs_offset; * * Routine to compute the offset of one phase * * Input: %0=the targeted AD channel * * %1=the offset structure dedicated to the phase * * %1=most significant word * * %1+1=less significant word * * Output: Current Offset structure * * Modified: * ***************************************************************************************} .MACRO EvaluateIs_offset(%0, %1); ADC_Read(%0); I1 = ^%1; M1 = 1; L1 = %%1; a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 15 of 18 CALL EvaluateIs_offset_; .ENDMACRO; The macro OffsetDetermination computes the offsets of the both A/D channels that measure the phase currents. {*************************************************************************************** * Type: Macro * * Call: OffsetDetermination * * Routine to compute the offsets introduced by the current sensors * * Input: %0=ADC1 * * %1=ADC2 * * %2=Is1Offset * * %3=Is2Offset * * Output: Current Offsets structure * * Modified: * ***************************************************************************************} .MACRO OffsetDetermination(%0, %1, %2, %3); AY0 = dm(OffsetCounter); AR = AY0 - 1; IF LT JUMP SaveOffsets; dm(OffsetCounter) = AR; EvaluateIs_offset(%0, TempOffset1); EvaluateIs_offset(%1, TempOffset2); RTI; SaveOffsets: AF = AR + 1; IF NE JUMP ExitOffsetDet; dm(OffsetCounter) = AR; AR = dm(TempOffset1); dm(%2) = AR; AR = dm(TempOffset2); dm(%3) = AR; ExitOffsetDet: .ENDMACRO; The macro ReadCurrents reads the two phase currents, corrects them with the offset and finally computes the third phase current. It may be noted that the output of the A/D converter is always a positive number. Because of the presence of an inverting operational amplifier in the hardware, in order to obtain values between –1/2 and +1/2 (in fixed point the currents are scaled by 2⋅ Imax ) the outputs of the A/D converter have to be offset by 1/2 (0x4000). {*************************************************************************************** * Type: Macro * * Call: ReadCurrents; * a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 16 of 18 * This macro reads ADC1(Is1), ADC2(Is2) and then evaluates Is1, Is2 and Is3 * * Input: %0 = offset of the first phase current * * %1 = offset of the second phase current * * %2 = the buffer of the 3 phase currents * * %3 = ADC1 * * %4 = ADC2 * * Output: none * * Modified: AY0, AR, MY0, MR, SR ,AF * ***************************************************************************************} .MACRO ReadCurrents(%0, %1, %2, %3, %4); ADC_Read(%3); { read Is1/Imax } AR = 0x4000 - AR; AY0 = dm(%0); AR = AR - AY0; dm(%2) = AR; { Is1/2Imax } ADC_Read(%4); { read Is2/Imax } AR = 0x4000 - AR; AY0 = dm(%1); AR = AR - AY0; dm(%2+1) = AR; { Is2/2Imax } AR = -AR; AY0 = dm(%2); { Is1/2Imax } AR = AR - AY0; dm(%2+2) = AR; { Is3/2Imax=-Is2/2Imax-Is1/2Imax} .ENDMACRO; 3.5 Experimental results First of all, experiments without the dead time compensation were made. Figure 4 represents the inverter phase voltage compared to the reference voltage that is desired at the inverter terminal and the phase current. It may be seen that the behavior presented in chapter 1 is verified in practice: When the phase current is positive, the real inverter phase voltage is less than the commanded one by an amount determined by the dead time and when the phase current is negative, the real inverter phase voltage is greater than the commanded. At last, Figure 5 displays the inverter phase voltage and the phase current obtained with the feed forward dead time compensation. It may be observed that the voltage still presents some distortions caused by the nature of feed forwarding: it is supposed that the current measured during the previous PWM cycle maintains its sign into the next PWM cycle; when the current changes the sign, this moment cannot be foreseen and the error is doubled. These voltage deformations cause also deformations in the current behaviour, and they may be prevented only implementing current controllers in a more accurate control strategy, like field-oriented control. a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 17 of 18 Figure 4. Reference and real inverter phase voltages and the phase current a Compensating the dead time of voltage inverters with the ADMC331 AN331-50 © Analog Devices Inc., August 2000 Page 18 of 18 Figure 5. Inverter phase voltage and phase current after the dead time compensation 4 References [1] Pulse dead time compensator for PWM voltage inverters, David Leggate, Russel J. Kerkman, Industrial Electronics, Control, and Instrumentation, 1995, Proceedings of the 1995 IEEE IECON 21st International Conference on Volume: 1, Page(s): 474 -481 vol.1. SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 1 􀀀 Wide Operating Voltage Range of 2 V to 6 V 􀀀 Typical Switch Enable Time of 18 ns 􀀀 Low Power Consumption, 20-μA Max ICC 􀀀 Low Input Current of 1 μA Max 􀀀 High Degree of Linearity 􀀀 High On-Off Output-Voltage Ratio 􀀀 Low Crosstalk Between Switches 􀀀 Low On-State Impedance . . . 50-Ω TYP at VCC = 6 V 􀀀 Individual Switch Controls description/ordering information The SN74HC4066 is a silicon-gate CMOS quadruple analog switch designed to handle both analog and digital signals. Each switch permits signals with amplitudes of up to 6 V (peak) to be transmitted in either direction. Each switch section has its own enable input control (C). A high-level voltage applied to C turns on the associated switch section. Applications include signal gating, chopping, modulation or demodulation (modem), and signal multiplexing for analog-to-digital and digital-to-analog conversion systems. ORDERING INFORMATION TA PACKAGE† ORDERABLE PART NUMBER TOP-SIDE MARKING PDIP – N Tube of 25 SN74HC4066N SN74HC4066N Tube of 50 SN74HC4066D SOIC – D Reel of 2500 SN74HC4066DR HC4066 Reel of 250 SN74HC4066DT –40°C to 85°C SOP – NS Reel of 2000 SN74HC4066NSR HC4066 SSOP – DB Reel of 2000 SN74HC4066DBR HC4066 Tube of 90 SN74HC4066PW TSSOP – PW Reel of 2000 SN74HC4066PWR HC4066 Reel of 250 SN74HC4066PWT † Package drawings, standard packing quantities, thermal data, symbolization, and PCB design guidelines are available at www.ti.com/sc/package. FUNCTION TABLE (each switch) INPUT CONTROL (C) SWITCH L OFF H ON PRODUCTION DATA information is current as of publication date. Copyright  2003, Texas Instruments Incorporated Products conform to specifications per the terms of Texas Instruments standard warranty. Production processing does not necessarily include testing of all parameters. Please be aware that an important notice concerning availability, standard warranty, and use in critical applications of Texas Instruments semiconductor products and disclaimers thereto appears at the end of this data sheet. 1 2 3 4 5 6 7 14 13 12 11 10 9 8 1A 1B 2B 2A 2C 3C GND VCC 1C 4C 4A 4B 3B 3A D, DB, N, NS, OR PW PACKAGE (TOP VIEW) SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 2 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 logic diagram, each switch (positive logic) A VCC VCC B One of Four Switches C absolute maximum ratings over operating free-air temperature range (unless otherwise noted)† Supply voltage range, VCC (see Note 1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . –0.5 V to 7 V Control-input diode current, II (VI < 0 or VI > VCC) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ±20 mA I/O port diode current, II (VI < 0 or VI/O > VCC) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ±20 mA On-state switch current (VI/O = 0 to VCC) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ±25 mA Continuous current through VCC or GND . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ±50 mA Package thermal impedance, θJA (see Note 2): D package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86°C/W DB package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96°C/W N package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80°C/W NS package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76°C/W PW package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113°C/W Storage temperature range, Tstg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . –65°C to 150°C † Stresses beyond those listed under “absolute maximum ratings” may cause permanent damage to the device. These are stress ratings only, and functional operation of the device at these or any other conditions beyond those indicated under “recommended operating conditions” is not implied. Exposure to absolute-maximum-rated conditions for extended periods may affect device reliability. NOTES: 1. All voltages are with respect to ground unless otherwise specified. 2. The package thermal impedance is calculated in accordance with JESD 51-7. SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 3 recommended operating conditions (see Note 3) MIN NOM MAX UNIT VCC Supply voltage 2† 5 6 V VI/O I/O port voltage 0 VCC V VCC = 2 V 1.5 VCC VIH High-level input voltage, control inputs VCC = 4.5 V 3.15 VCC V VCC = 6 V 4.2 VCC VCC = 2 V 0 0.3 VIL Low-level input voltage, control inputs VCC = 4.5 V 0 0.9 V VCC = 6 V 0 1.2 VCC = 2 V 1000 Δt/Δv Input transition rise/fall time VCC = 4.5 V 500 ns VCC = 6 V 400 TA Operating free-air temperature –40 85 °C † With supply voltages at or near 2 V, the analog switch on-state resistance becomes very nonlinear. It is recommended that only digital signals be transmitted at these low supply voltages. NOTE 3: All unused inputs of the device must be held at VCC or GND to ensure proper device operation. Refer to the TI application report, Implications of Slow or Floating CMOS Inputs, literature number SCBA004. electrical characteristics over recommended operating free-air temperature range (unless otherwise noted) PARAMETER TEST CONDITIONS V TA = 25􀀀C VCC MIN MAX UNIT MIN TYP MAX I A V 0t V 2 V 150 ron On-state switch resistance IT = –1 mA, VI = 0 to VCC, 4.5 V 50 85 106 Ω VC = VIH (see Figure 1) 6 V 30 V V GND V V 2 V 320 ron(p) Peak on-state resistance VI = VCC or GND, VC = VIH, ( ) 4.5 V 70 170 215 Ω IT = –1 mA 6 V 50 II Control input current VC = 0 or VCC 6 V ±0.1 ±100 ±1000 nA Isoff Off-state switch leakage current VI = VCC or 0, VO = VCC or 0, VC = VIL (see Figure 2) 6 V ±0.1 ±5 μA Ison On-state switch leakage current VI = VCC or 0, VC = VIH (see Figure 3) 6 V ±0.1 ±5 μA ICC Supply current VI = 0 or VCC, IO = 0 6 V 2 20 μA Ci Input capacitance A or B 5 V 9 pF C 3 10 10 Cf Feed-through capacitance A to B VI = 0 0.5 pF Co Output capacitance A or B 5 V 9 pF SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 4 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 switching characteristics over recommended operating free-air temperature range PARAMETER FROM TO TEST VCC TA = 25􀀀C MIN MAX UNIT (INPUT) (OUTPUT) CONDITIONS MIN TYP MAX t P ti C 50 F 2 V 10 60 75 tPLH, Propagation A or B B or A CL = pF 4.5 V 4 12 15 ns tPHL delay time (see Figure 4) 6 V 3 10 13 t S it h RL = 1 kΩ, 2 V 70 180 225 tPZH, tPZL Switch turn-on time C A or B CL = 50 pF 4.5 V 21 36 45 ns L (see Figure 5) 6 V 18 31 38 t S it h RL = 1 kΩ, 2 V 50 200 250 tPLZ, Switch C A or B CL = 50 pF 4.5 V 25 40 50 ns tPHZ turn-off time L (see Figure 5) 6 V 22 34 43 Control CL = 15 pF, RL = 1 kΩ 2 V 15 fI input frequency C A or B kΩ, VC = VCC or GND, V V /2 4.5 V 30 MHz VO = VCC/(see Figure 6) 6 V 30 Control feed-through C A or B CL = 50 pF, Rin = RL = 600 Ω, VC = VCC or GND 4.5 V 15 mV noise GND, fin = 1 MHz (see Figure 7) 6 V 20 (rms) operating characteristics, VCC = 4.5 V, TA = 25°C PARAMETER TEST CONDITIONS TYP UNIT Cpd Power dissipation capacitance per gate CL = 50 pF, f = 1 MHz 45 pF Minimum through bandwidth, A to B or B to A† [20 log (VO/VI)] = –3 dB CL = 50 pF, VC = VCC RL = 600 Ω, (see Figure 8) 30 MHz Crosstalk between any switches‡ CL = 10 pF, fin = 1 MHz RL = 50 Ω, (see Figure 9) 45 dB Feed through, switch off, A to B or B to A‡ CL = 50 pF, fin = 1 MHz RL = 600 Ω, (see Figure 10) 42 dB Amplitude distortion rate, A to B or B to A CL = 50 pF, fin = 1 kHz RL = 10 kΩ, (see Figure 11) 0.05% † Adjust the input amplitude for output = 0 dBm at f = 1 MHz. Input signal must be a sine wave. ‡ Adjust the input amplitude for input = 0 dBm at f = 1 MHz. Input signal must be a sine wave. SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 5 PARAMETER MEASUREMENT INFORMATION VCC VI = VCC VC = VIH + 1.0 mA – VO ron 􀀀 VI–O 10–3 􀀀 VI–O VCC GND (ON) V Figure 1. On-State Resistance Test Circuit VCC VC = VIL A B VS = VA – VB CONDITION 1: VA = 0, VB = VCC CONDITION 2: VA = VCC, VB = 0 VCC GND A (OFF) Figure 2. Off-State Switch Leakage-Current Test Circuit SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 6 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 PARAMETER MEASUREMENT INFORMATION VCC VC = VIH A B VCC Open VA = VCC TO GND VCC GND A (ON) Figure 3. On-State Leakage-Current Test Circuit VCC VC = VIH VI VO 50 pF TEST CIRCUIT tPLH tPHL 50% 50% VCC 0 V 50% 50% VOH VOL VI A or B VO B or A VOLTAGE WAVEFORMS 50 Ω tr 90% 10% tf 10% 90% VCC GND (ON) Figure 4. Propagation Delay Time, Signal Input to Signal Output SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 7 PARAMETER MEASUREMENT INFORMATION CL GND 50 pF VCC VI VO TEST CIRCUIT tPLZ 50% VOLTAGE WAVEFORMS RL 1 kΩ 10% S1 VC 50 Ω S2 tPZH tPHZ 50% 50% 50% 90% tPZL tPZH tPLZ tPHZ GND VCC GND VCC TEST S1 S2 VCC GND VCC GND tPZL 50% VCC VO 50% 0 V VOL VOH VC (tPZL, tPZH) (tPLZ, tPHZ) VCC VCC VO 0 V VOL VOH VC VCC 0 V VOL VOH VCC 0 V VOL VOH Figure 5. Switching Time (tPZL, tPLZ, tPZH, tPHZ), Control to Signal Output SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 8 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 PARAMETER MEASUREMENT INFORMATION VCC GND VO RL 1 kΩ CL 15 pF VCC VC 50 Ω VI = VCC VCC VC 0 V VCC/2 Figure 6. Control-Input Frequency VCC GND VO RL 600 Ω CL 50 pF VCC VC 50 Ω VI VCC/2 Rin 600 Ω VCC/2 tr tf 90% 10% (f = 1 MHz) tr = tf = 6 ns 90% 10% VCC VC 0 V Figure 7. Control Feed-Through Noise VO VCC 50 Ω fin VCC/2 VC = VCC 0.1 μF VI VI (VI = 0 dBm at f = 1 MHz) VCC GND (ON) RL 600 Ω CL 50 pF Figure 8. Minimum Through Bandwidth SN74HC4066 QUADRUPLE BILATERAL ANALOG SWITCH SCLS325G – MARCH 1996 – REVISED JULY 2003 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 9 PARAMETER MEASUREMENT INFORMATION VO1 RL 600 Ω CL 50 pF VCC 50 Ω fin VCC/2 VC = VCC 0.1 μF VI VI (VI = 0 dBm at f = 1 MHz) VO2 VCC Rin 600 Ω VCC/2 VC = GND Rin 600 Ω VCC GND (ON) VCC GND (OFF) RL 600 Ω CL 50 pF Figure 9. Crosstalk Between Any Two Switches VO VCC 50 Ω fin VC = GND 0.1 μF VI VI (VI = 0 dBm at f = 1 MHz) VCC GND (OFF) Rin 600 Ω RL 600 Ω CL 50 pF VCC/2 VCC/2 Figure 10. Feed Through, Switch Off VI (VI = 0 dBm at f = 1 kHz) VO RL 10 kΩ CL 50 pF VCC VCC/2 VC = VCC 10 μF VI fin VCC GND (ON) Figure 11. Amplitude-Distortion Rate PACKAGE OPTION ADDENDUM www.ti.com 10-Jun-2014 Addendum-Page 1 PACKAGING INFORMATION Orderable Device Status (1) Package Type Package Drawing Pins Package Qty Eco Plan (2) Lead/Ball Finish (6) MSL Peak Temp (3) Op Temp (°C) Device Marking (4/5) Samples SN74HC4066D ACTIVE SOIC D 14 50 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066DBLE OBSOLETE SSOP DB 14 TBD Call TI Call TI -40 to 85 SN74HC4066DBR ACTIVE SSOP DB 14 2000 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066DBRE4 ACTIVE SSOP DB 14 2000 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066DG4 ACTIVE SOIC D 14 50 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066DR ACTIVE SOIC D 14 2500 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066DRE4 ACTIVE SOIC D 14 2500 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066DRG4 ACTIVE SOIC D 14 2500 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066DT ACTIVE SOIC D 14 250 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066N ACTIVE PDIP N 14 25 Pb-Free (RoHS) CU NIPDAU N / A for Pkg Type -40 to 85 SN74HC4066N SN74HC4066NE4 ACTIVE PDIP N 14 25 Pb-Free (RoHS) CU NIPDAU N / A for Pkg Type -40 to 85 SN74HC4066N SN74HC4066NSR ACTIVE SO NS 14 2000 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066PW ACTIVE TSSOP PW 14 90 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066PWG4 ACTIVE TSSOP PW 14 90 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066PWLE OBSOLETE TSSOP PW 14 TBD Call TI Call TI -40 to 85 SN74HC4066PWR ACTIVE TSSOP PW 14 2000 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066PWRG4 ACTIVE TSSOP PW 14 2000 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 SN74HC4066PWT ACTIVE TSSOP PW 14 250 Green (RoHS & no Sb/Br) CU NIPDAU Level-1-260C-UNLIM -40 to 85 HC4066 PACKAGE OPTION ADDENDUM www.ti.com 10-Jun-2014 Addendum-Page 2 (1) The marketing status values are defined as follows: ACTIVE: Product device recommended for new designs. LIFEBUY: TI has announced that the device will be discontinued, and a lifetime-buy period is in effect. NRND: Not recommended for new designs. Device is in production to support existing customers, but TI does not recommend using this part in a new design. PREVIEW: Device has been announced but is not in production. Samples may or may not be available. OBSOLETE: TI has discontinued the production of the device. (2) Eco Plan - The planned eco-friendly classification: Pb-Free (RoHS), Pb-Free (RoHS Exempt), or Green (RoHS & no Sb/Br) - please check http://www.ti.com/productcontent for the latest availability information and additional product content details. TBD: The Pb-Free/Green conversion plan has not been defined. Pb-Free (RoHS): TI's terms "Lead-Free" or "Pb-Free" mean semiconductor products that are compatible with the current RoHS requirements for all 6 substances, including the requirement that lead not exceed 0.1% by weight in homogeneous materials. Where designed to be soldered at high temperatures, TI Pb-Free products are suitable for use in specified lead-free processes. Pb-Free (RoHS Exempt): This component has a RoHS exemption for either 1) lead-based flip-chip solder bumps used between the die and package, or 2) lead-based die adhesive used between the die and leadframe. The component is otherwise considered Pb-Free (RoHS compatible) as defined above. Green (RoHS & no Sb/Br): TI defines "Green" to mean Pb-Free (RoHS compatible), and free of Bromine (Br) and Antimony (Sb) based flame retardants (Br or Sb do not exceed 0.1% by weight in homogeneous material) (3) MSL, Peak Temp. - The Moisture Sensitivity Level rating according to the JEDEC industry standard classifications, and peak solder temperature. (4) There may be additional marking, which relates to the logo, the lot trace code information, or the environmental category on the device. (5) Multiple Device Markings will be inside parentheses. Only one Device Marking contained in parentheses and separated by a "~" will appear on a device. If a line is indented then it is a continuation of the previous line and the two combined represent the entire Device Marking for that device. (6) Lead/Ball Finish - Orderable Devices may have multiple material finish options. Finish options are separated by a vertical ruled line. Lead/Ball Finish values may wrap to two lines if the finish value exceeds the maximum column width. Important Information and Disclaimer:The information provided on this page represents TI's knowledge and belief as of the date that it is provided. TI bases its knowledge and belief on information provided by third parties, and makes no representation or warranty as to the accuracy of such information. Efforts are underway to better integrate information from third parties. TI has taken and continues to take reasonable steps to provide representative and accurate information but may not have conducted destructive testing or chemical analysis on incoming materials and chemicals. TI and TI suppliers consider certain information to be proprietary, and thus CAS numbers and other limited information may not be available for release. In no event shall TI's liability arising out of such information exceed the total purchase price of the TI part(s) at issue in this document sold by TI to Customer on an annual basis. TAPE AND REEL INFORMATION *All dimensions are nominal Device Package Type Package Drawing Pins SPQ Reel Diameter (mm) Reel Width W1 (mm) A0 (mm) B0 (mm) K0 (mm) P1 (mm) W (mm) Pin1 Quadrant SN74HC4066DBR SSOP DB 14 2000 330.0 16.4 8.2 6.6 2.5 12.0 16.0 Q1 SN74HC4066DR SOIC D 14 2500 330.0 16.4 6.5 9.0 2.1 8.0 16.0 Q1 SN74HC4066DT SOIC D 14 250 330.0 16.4 6.5 9.0 2.1 8.0 16.0 Q1 SN74HC4066NSR SO NS 14 2000 330.0 16.4 8.2 10.5 2.5 12.0 16.0 Q1 SN74HC4066PWR TSSOP PW 14 2000 330.0 12.4 6.9 5.6 1.6 8.0 12.0 Q1 SN74HC4066PWT TSSOP PW 14 250 330.0 12.4 6.9 5.6 1.6 8.0 12.0 Q1 PACKAGE MATERIALS INFORMATION www.ti.com 14-Jul-2012 Pack Materials-Page 1 *All dimensions are nominal Device Package Type Package Drawing Pins SPQ Length (mm) Width (mm) Height (mm) SN74HC4066DBR SSOP DB 14 2000 367.0 367.0 38.0 SN74HC4066DR SOIC D 14 2500 367.0 367.0 38.0 SN74HC4066DT SOIC D 14 250 367.0 367.0 38.0 SN74HC4066NSR SO NS 14 2000 367.0 367.0 38.0 SN74HC4066PWR TSSOP PW 14 2000 367.0 367.0 35.0 SN74HC4066PWT TSSOP PW 14 250 367.0 367.0 35.0 PACKAGE MATERIALS INFORMATION www.ti.com 14-Jul-2012 Pack Materials-Page 2 MECHANICAL DATA MSSO002E – JANUARY 1995 – REVISED DECEMBER 2001 POST OFFICE BOX 655303 • DALLAS, TEXAS 75265 DB (R-PDSO-G**) PLASTIC SMALL-OUTLINE 4040065 /E 12/01 28 PINS SHOWN Gage Plane 8,20 7,40 0,55 0,95 0,25 38 12,90 12,30 28 10,50 24 8,50 Seating Plane 7,90 9,90 30 10,50 9,90 0,38 5,60 5,00 15 0,22 14 A 28 1 16 20 6,50 6,50 14 0,05 MIN 5,90 5,90 DIM A MAX A MIN PINS ** 2,00 MAX 6,90 7,50 0,65 0,15 M 0°–8° 0,10 0,09 0,25 NOTES: A. All linear dimensions are in millimeters. B. This drawing is subject to change without notice. C. Body dimensions do not include mold flash or protrusion not to exceed 0,15. D. Falls within JEDEC MO-150 IMPORTANT NOTICE Texas Instruments Incorporated and its subsidiaries (TI) reserve the right to make corrections, enhancements, improvements and other changes to its semiconductor products and services per JESD46, latest issue, and to discontinue any product or service per JESD48, latest issue. Buyers should obtain the latest relevant information before placing orders and should verify that such information is current and complete. All semiconductor products (also referred to herein as “components”) are sold subject to TI’s terms and conditions of sale supplied at the time of order acknowledgment. TI warrants performance of its components to the specifications applicable at the time of sale, in accordance with the warranty in TI’s terms and conditions of sale of semiconductor products. Testing and other quality control techniques are used to the extent TI deems necessary to support this warranty. Except where mandated by applicable law, testing of all parameters of each component is not necessarily performed. TI assumes no liability for applications assistance or the design of Buyers’ products. Buyers are responsible for their products and applications using TI components. To minimize the risks associated with Buyers’ products and applications, Buyers should provide adequate design and operating safeguards. TI does not warrant or represent that any license, either express or implied, is granted under any patent right, copyright, mask work right, or other intellectual property right relating to any combination, machine, or process in which TI components or services are used. Information published by TI regarding third-party products or services does not constitute a license to use such products or services or a warranty or endorsement thereof. Use of such information may require a license from a third party under the patents or other intellectual property of the third party, or a license from TI under the patents or other intellectual property of TI. Reproduction of significant portions of TI information in TI data books or data sheets is permissible only if reproduction is without alteration and is accompanied by all associated warranties, conditions, limitations, and notices. TI is not responsible or liable for such altered documentation. Information of third parties may be subject to additional restrictions. Resale of TI components or services with statements different from or beyond the parameters stated by TI for that component or service voids all express and any implied warranties for the associated TI component or service and is an unfair and deceptive business practice. TI is not responsible or liable for any such statements. Buyer acknowledges and agrees that it is solely responsible for compliance with all legal, regulatory and safety-related requirements concerning its products, and any use of TI components in its applications, notwithstanding any applications-related information or support that may be provided by TI. Buyer represents and agrees that it has all the necessary expertise to create and implement safeguards which anticipate dangerous consequences of failures, monitor failures and their consequences, lessen the likelihood of failures that might cause harm and take appropriate remedial actions. Buyer will fully indemnify TI and its representatives against any damages arising out of the use of any TI components in safety-critical applications. In some cases, TI components may be promoted specifically to facilitate safety-related applications. With such components, TI’s goal is to help enable customers to design and create their own end-product solutions that meet applicable functional safety standards and requirements. Nonetheless, such components are subject to these terms. No TI components are authorized for use in FDA Class III (or similar life-critical medical equipment) unless authorized officers of the parties have executed a special agreement specifically governing such use. Only those TI components which TI has specifically designated as military grade or “enhanced plastic” are designed and intended for use in military/aerospace applications or environments. Buyer acknowledges and agrees that any military or aerospace use of TI components which have not been so designated is solely at the Buyer's risk, and that Buyer is solely responsible for compliance with all legal and regulatory requirements in connection with such use. TI has specifically designated certain components as meeting ISO/TS16949 requirements, mainly for automotive use. In any case of use of non-designated products, TI will not be responsible for any failure to meet ISO/TS16949. Products Applications Audio www.ti.com/audio Automotive and Transportation www.ti.com/automotive Amplifiers amplifier.ti.com Communications and Telecom www.ti.com/communications Data Converters dataconverter.ti.com Computers and Peripherals www.ti.com/computers DLP® Products www.dlp.com Consumer Electronics www.ti.com/consumer-apps DSP dsp.ti.com Energy and Lighting www.ti.com/energy Clocks and Timers www.ti.com/clocks Industrial www.ti.com/industrial Interface interface.ti.com Medical www.ti.com/medical Logic logic.ti.com Security www.ti.com/security Power Mgmt power.ti.com Space, Avionics and Defense www.ti.com/space-avionics-defense Microcontrollers microcontroller.ti.com Video and Imaging www.ti.com/video RFID www.ti-rfid.com OMAP Applications Processors www.ti.com/omap TI E2E Community e2e.ti.com Wireless Connectivity www.ti.com/wirelessconnectivity Mailing Address: Texas Instruments, Post Office Box 655303, Dallas, Texas 75265 Copyright © 2014, Texas Instruments Incorporated a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 1 of 11 a Using a Tracebuffer with the ADMCF32X ANF32X-34 a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 2 of 11 Table of Contents SUMMARY...................................................................................................................... 3 1 THE TRACEBUFFER STRUCTURE........................................................................ 3 1.1 The Tracebuffer Data-Array.........................................................................................................................4 2 IMPLEMENTATION OF THE TRACEBUFFER LIBRARY ROUTINES ................... 5 2.1 Usage of the tracebuffer routines ..................................................................................................................5 2.2 Usage of the DSP registers .............................................................................................................................5 2.3 Access to the library: the header file.............................................................................................................6 2.4 The program macro........................................................................................................................................7 3 SOFTWARE EXAMPLE: TRACEBUFFER.............................................................. 8 3.1 Usage of the Tracebuffer routine an example ..............................................................................................8 3.2 The main program: main.dsp........................................................................................................................8 4 EXPERIMENTAL RESULTS.................................................................................. 10 a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 3 of 11 Summary In many cases the plotting and processing data externally are needed to verify and debug code and structure in a DSP. This application note describe the use of a tracebuffer structure where values treated in the DSP can be saved in a data-array and used for internal of external modification interfaced though the Motion Control Debugger system. 1 The Tracebuffer Structure A data-array structure is defined to enable saving arrays of values in data-memory (DM). This array of memory locations can be addressed by the use of the pointer-system on the 2171 core. With this structure defined, further treating or evaluation of the internal data-calculations can be analyzed and checked for errors. Using the Motion Control Debugger the values can be either be plotted directly or dumped for analyzing the data-array in other external programs In the chosen structure any number of pointer arrays in DM can be enabled and individually initialized for locations in DM. The structure will furthermore allow the user to under-sample the writing to the buffer. Initialize the Tracebuffer Though macro Is the Sample Ratio = Sample number? Is Flag enabled ? Is there still space in the Buffer Full ? YES No YES No Update Buffer and increment pointer and counter End Macro; Macro Call YES No Figure 1 - Flowchart for the Buffer writing The flow chart illustrate the structure of the trace buffer writing. Initialization is done in the startup sequence. After this, the Flag is checked - is the flag set then the corresponding tracebuffer is enabled. Secondly the buffer is checked for available spaces. If the DM locations defined for memory write aren't full it is safe to go on. If the buffer is full return. Finally the sample-ratio is checked. If a sample-ratio is a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 4 of 11 declared different from zero then check if the sample-number is equal to the sample-ratio. If it is write the chosen variable to the data-array. If not, return to the subroutine. The structure of the buffer is circular and to optimize the flexibility the format is provided as a complete macro setting with locked data-array format. 1.1 The Tracebuffer Data-Array To enable the tracebuffer array in DM it is necessary to define a given circular buffer with associated pointer. The circular buffer is structured as: First location : Statement of flag - ON/OFF Second location : Pointer to next free address Third location : Sample ratio (specified by the user) Fourth location : Sample number (used during the re-sampling of values) Fifth location : Counter for the buffer. Sixth to XXX locations : Placement for the values Every time the macro is called, Ex. in the PWMSYNC_ISR, a new value is added to the buffer if there are available space left and the sample number is equal to the under-sample ratio. DM(Address) Flag (ON/OFF) DM(Address+1) Pointer to next free address .. Sample ratio .. Sample number .. Counter for Buffer .. First Data-placement .. Value(1) .. Value(2) .. .. .. .. .. .. .. Value (Buffer size -2) .. Value (Buffer size -1) .. Value (Buffer size) Figure 2 - Tracebuffer - locations in DM Figure 2 illustrates how the values are placed in the allocated DM locations. Here values are stored at specific addresses in order to analyze these off-line. First value Placed in the buffer N = numbers in tracebuffer Buffer full a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 5 of 11 2 Implementation of the Tracebuffer Library Routines 2.1 Usage of the tracebuffer routines The routines are developed as an easy-to-use library, which has to be linked to the user’s application. The library consists of two files. The file “T_buffer.dsp” contains the assembly code for the subroutines. This package has to be compiled and can then be linked to an application. The user has to include the header file “T_buffer.h”, which provides the function-like macros for this routine. The following table summarizes the set of macros that are defined in this library. Operation Usage Input Output Initialization Buffer_Init("name", sample ratio); Name & Sample ratio None Activate Buffer_ON("name"); Name None Deactivate Buffer_OFF("name"); Name None Record Buffer_Record("name", value); Name & Value None Table 1: Implemented routines The four-macro settings allow the user to setup any given DM-locations for trace-buffer availability. Specifying the selected buffer and record value enables the flexibility of writing any number to a known position in memory. 2.2 Usage of the DSP registers Table 2 gives an overview of the DSP core registers that are modified by the four macros mentioned above. Obviously, also the "input" values are modified. Usage Modified registers Buffer_Init("name", sample ratio); ax0 Buffer_OFF("name"); ax0 Buffer_ON("name"); ax0 Buffer_Record("name", value); ax0, ax1, ay0, ar, I5, M5 Table 2: Usage of DSP core registers for the subroutines a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 6 of 11 2.3 Access to the library: the header file Including the header file "t_buffer.h" into the application code may access the library. The header file is intended to provide function-like macros to the Trace buffer routines. It defines the calls shown in Table 1. The file is mostly self-explaining but some comments have to be added. The sample ratio is here defined as how often is a new value can be written to the buffer. First macro is the Buffer_Init macro. This macro initializes the five first location of the circular buffer in respect to "name of the buffer" and the sample-ratio. Furthermore the sample-number and the internal counter is cleared. The second and third macro Buffer_ON and Buffer_OFF just enables or disables writing to the buffers. In this case the first location in the buffer ( the flag ) are set/or cleared. {******************************************************************************** * * * Type: Macro * * * * Call: Buffer_Init("Buffer", sampleratio) * * Description : Initialize the tracebuffer * * * * Undersample ratio 0 = every time * * 1 = every 1.time * * 2 = every 2.time ..... * * * * Ouputs : none * * * * Modified: ax0 * * * ********************************************************************************} .MACRO Buffer_Init(%0,%1); ax0 = %1; { Sample ratio } dm(%0+2)= ax0; ax0 =^%0+5; { Store start value } dm(%0+1)= ax0; { first location for data } ax0 = 0x0000; dm(%0) = ax0; { Clear Flag - Non-Active } dm(%0+3)= ax0; { Clear sample number } dm(%0+4)= ax0; { Clear counter for this buffer } .ENDMACRO; {******************************************************************************** * * * Type: Macro * * * * Call: Buffer_ON("buffer") * * * * Description : Enable tracebuffer "Buffer" * * Ouputs : none * * * * Modified : ax0 * * * ********************************************************************************} .MACRO Buffer_ON(%0); ax0 = 1; dm(%0) = ax0; .ENDMACRO; a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 7 of 11 {******************************************************************************** * * * Type: Macro * * * * Call: Buffer_OFF("buffer") * * * * Description : Disable tracebuffer "Buffer" * * Ouputs : none * * * * Modified : ax0 * * * ********************************************************************************} .MACRO Buffer_OFF(%0); ax0 = 0; dm(%0) = ax0; .ENDMACRO; 2.4 The program macro The following code contained in the file “t_buffer.h” defines the macrocode used for the Tracebuffer. In many cases this piece of code is placed in the "t_buffer.dsp"-file but here the flexibility advances by placing the program-code directly in the macro. It should be mentioned that this way of using the tracebuffer enables flexibility but takes up more memory. The following code implements the tracebuffer routines. Refer to the flowchart in section 1 for the structure of the buffers. Input to the tracebuffer are any numbers computed in the DSP. Underneath is the code for the Buffer_Record.. It just need to be said that since the buffer is structured as a circular buffer the data-placement for each of the "buffer-handle" values are placed from buffer-location 1 to 5 (here %0….%0+4) {******************************************************************************** * * * Type: Macro * * * * all: Buffer_Record(buffer,data) * * * * Description : Place data in buffer memory * * Ouputs : none * * * * Modified: M5, I5, ar, ax1, ax0, ay0 * * * ********************************************************************************} .MACRO Buffer_Record(%0,%1); .Local Continue1,Continue2,Continue3,End; { Local routines in Macro } M5 = 1; { modify factor = 1 } ax1 = %1; I5 = ^%0; { load start value for pointer } ar = dm(%0); { temporary storage } ar= tstbit 0 of ar; if NE jump Continue1; Jump end; Continue1: ax0 = %%0; ay0 = dm(%0+4); ar = ax0 - ay0; if gt jump Continue2; ax0 = 0x0000; dm(%0) = ax0; Jump end; a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 8 of 11 Continue2: { is sample_num equal to ratio? } ax0 = dm(%0+3); ay0 = dm(%0+2); ar = ax0 - ay0; if eq jump Continue3; ar = ax0 + 1; dm(%0+3) = ar; Jump end; Continue3: { write into buffer } I5 = dm(%0+1); { load backup value for pointer } dm(I5,M5) = ax1; { Value updated to Buffer } ax0 = dm(%0+4); { increment count } ar = ax0 + 1; dm(%0+4) = ar; ax0 = 0x0000; { clear sample_num } dm(%0+3) = ax0; dm(%0+1) = I5; end: .ENDMACRO; 3 Software Example: Tracebuffer 3.1 Usage of the Tracebuffer routine an example This example demonstrates how two values are written to Buffer1 and Buffer2. In this case the memorylocations used as buffers are set to 2*105-locations (100 location of calculated data). The values written to these two buffer-arrays are values computed for three 120-degree phase shifted reference voltages. 3.2 The main program: main.dsp The file “main.dsp” contains the initialisation and PWM Sync and Trip interrupt service routines. To activate, build the executable file using the attached build.bat either within your DOS prompt or clicking on it from Windows Explorer. This will create the object files and the main.exe example file. This file may be run on the Motion Control Debugger. The program can be booted from Flash but in this tracebuffer case it is not effectuated since the DM can not be read without the Motion Control Debugger. Every module besides from the Main_program module is by default placed in either one of the three USERFLASH memory banks. In the following, a brief description of the code is given. Start of code – declaring start location in program memory or FLASH memory. Comments are placed depending on whether the program should run in PMRAM or Flash memory. {************************************************************************************** * Application: Starting from FLASH (out-comment the one not used) **************************************************************************************} !.MODULE/RAM/SEG=USERFLASH1/ABS=0x2200 Main_Program; {************************************************************************************** * Application: Starting from RAM (out-comment the one not used) **************************************************************************************} .MODULE/RAM/SEG=USER_PM1/ABS=0x30 Main_Program; a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 9 of 11 Next, the general systems constants and PWM configuration constants (main.h – see the next section) are included. Also included are the PWM library and the T_BUFFER library definitions {******************************************************************************** * Include General System Parameters and Libraries * ********************************************************************************} #include ; #include ; #include ; #include ; {******************************************************************************** * Local Variables Defined in this Module * ********************************************************************************} .VAR/DM/RAM/SEG=USER_DM AD_IN; { Volts/Hertz Command (0-1) } .VAR/DM/RAM/SEG=USER_DM Theta; { Current angle } .VAR/DM/RAM/SEG=USER_DM VrefA; { Voltage demands } .VAR/DM/RAM/SEG=USER_DM VrefB; .VAR/DM/RAM/SEG=USER_DM VrefC; .VAR/DM/RAM/CIRC/SEG=USER_DM Buffer1[105]; { Tracebuffer } .VAR/DM/RAM/CIRC/SEG=USER_DM Buffer2[105]; { Tracebuffer } ar = 0x7FFF; dm(AD_IN) = ar; ar = 0x0000; dm(Theta) = ar; dm(VrefA) = ar; dm(VrefB) = ar; dm(VrefC) = ar; Some Variables are defined hereafter. These are used to calculate the three reference voltages. For further information see ANF32X-3. The two circular buffers are defined - here the size is 105 locations (5 locations are used for handling the buffer) this number is arbitrary - just depending on the memory locations occupied by these buffers. The first thing that is done in the initialisation block (Startup) is checking a selected PIO line for level. If the PIO-pin is high jump to an ERASE BOOT FROM FLASH BIT routine in ROM and return. If not, just go ahead with normal operation. This small macro is done to enable re-coding of the FLASH memory. For further information (See Reference Manual). In this example the PIO-pin 6 is chosen as erase pin. The initialisation of the PWM block is executed. Note how the interrupt vectors for the PWMSync and PWMTrip service routines are passed as arguments. Then the interrupt IRQ2 is enabled by setting the corresponding bit in the IMASK register. Two Tracebuffers are initialised with 1x under-sampling Then the Tracebuffers are activated by setting the flag (Buffer_ON(Buffer1) & Buffer_ON(Buffer2)). After that, the program enters a loop, which just waits for interrupts. {******************************************************************************** * Start of program code * ********************************************************************************} Startup: FLASH_erase_PIO(6); { Select PIO6 as clearing PIO } { Remember that sport1 is muxed with the PIO-lines } { If the bit is high Clear Memory and Boot from } { Flash bit } PWM_Init(PWMSYNC_ISR, PWMTRIP_ISR); IFC = 0x80; { Clear any pending IRQ2 inter. } ay0 = 0x200; { unmask irq2 interrupts. } ar = IMASK; ar = ar or ay0; IMASK = ar; { IRQ2 ints fully enabled here } Buffer_Init(Buffer1, 1); { 1x undersampling } Buffer_Init(Buffer2, 1); { 1X undersampling } Buffer_ON(Buffer1); { Activate the Buffer } Buffer_ON(Buffer2); { Activate the Buffer } Main: { Wait for interrupt to occur } jump Main; rts; a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 10 of 11 In the PWMSYNC_ISR the DAGS are first set up for trigonometric functionality. Three reference voltages VrefA,B and C are calculated on base of the trigonometric functions in the Trigonometric-library ( See ANF32X-10 ). The PWM block is update with these control signals and finally the two Tracebuffers Buffer1 and Buffer2 are updated. Here the variables VrefA and VrefB are stored in the two data-arrays. PWMSYNC_ISR: Set_DAG_registers_for_trigonometric; my0 = DM(AD_IN); mr = 0; { Clear mr } mr1 = dm(Theta); { Preload Theta } mx0 = Delta; mr = mr + mx0*my0 (SS); { Compute new angle & store } dm(Theta) = mr1; Sin(mr1); { Result in ar register } mr = ar*my0 (SS); { Multiply by Scale for VrefA } dm(VrefA) = mr1; ax1 = dm(Theta); { Compute angle of phase B } ay1 = TwoPioverThree; ar = ax1 - ay1; Sin(ar); { Result in ar register } mr = ar*my0 (SS); { Multiply by Scale for VrefB } dm(VrefB) = mr1; ax1 = dm(Theta); { Compute angle of phase C } ay1 = TwoPioverThree; ar = ax1 + ay1; Sin(ar); { Result in ar register } mr = ar*my0 (SS); { Multiply by Scale for VrefC } dm(VrefC) = mr1; ax0 = DM(VrefA); ax1 = DM(VrefB); ay0 = DM(VrefC); ay1= DM(Theta); PWM_update_demanded_Voltage(ax0,ax1,ay0); {******************************************************************************* * Update tracebuffers * *******************************************************************************} ax0 = DM(VrefA); Buffer_Record(Buffer1,ax0); ax0 = DM(VrefB); Buffer_Record(Buffer2,ax0); RTI; It has to be mentioned that the Buffer_Record macro uses some DSP registers (see T_buffer.h) for that reason the proposed way of writing to the buffer is as defined above. 4 Experimental results The experimental results illustrated beneath are two plots of VrefA and VrefB. These values are written into Buffer1 and Buffer2 and then plotted though the Motion Control Debugger. As can be seen on Figure 3 the two waveforms are plotted as a function of the given number in Buffer1 and 2. From the figures the scaling can also be seen - here the numbers are represented in decimal. Selecting another scaling of these reference-voltages will re-scale these plots. a Using a Tracebuffer with the ADMCF32X ANF32X-34 © Analog Devices Inc., March 2000 Page 11 of 11 Figure 3 - Plot from the Motion Control Debugger using the Internal Plot Function. www.analog.com Developing VisualAudio Modules Copyright Information © 2006 Analog Devices, Inc., ALL RIGHTS RESERVED. This document may not be reproduced in any form without prior, express written consent from Analog Devices, Inc. Printed in the USA. Disclaimer Analog Devices, Inc. reserves the right to change this product without prior notice. Information furnished by Analog Devices is believed to be accurate and reliable. However, no responsibility is assumed by Analog Devices for its use; nor for any infringement of patents or other rights of third parties which may result from its use. No license is granted by implication or otherwise under the patent rights of Analog Devices, Inc. Trademark and Service Mark Notice The Analog Devices logo, VisualDSP++, VisualAudio, SHARC, Blackfin, and EZ-KIT Lite are registered trademarks of Analog Devices, Inc. All other brand and product names are trademarks or service marks of their respective owners. 2 of 51 Contents Contents..............................................................................................................................................................................................................3 Preface.................................................................................................................................................................................................................4 Purpose of This Manual................................................................................................................................................................................4 Custom Audio Modules....................................................................................................................................................................................5 Overview.........................................................................................................................................................................................................5 Numerics on the Blackfin and SHARC.......................................................................................................................................................9 Example 1A – Mono Parametric Scaling....................................................................................................................................................9 Example 1B – Render Function in ASM.................................................................................................................................................19 Scratch Buffers............................................................................................................................................................................................22 Auxiliary Memory for Module Instances................................................................................................................................................22 Pointer Aliasing Rules................................................................................................................................................................................25 Meta-Variables and Expressions...............................................................................................................................................................26 Modifying Module Parameters.................................................................................................................................................................27 Expression Language Details.....................................................................................................................................................................28 Modules With Data of Varying Size.........................................................................................................................................................33 Modules With a Variable Number of Pins...............................................................................................................................................34 Frequency Domain Processing.................................................................................................................................................................36 Other Features of the XML File................................................................................................................................................................36 Custom Bypass Functions..........................................................................................................................................................................38 SHARC SIMD Considerations..................................................................................................................................................................38 Adjusting Modules from Other Modules................................................................................................................................................39 Dynamically Changing a Module’s Render Function............................................................................................................................39 Compatibility between Blackfin and SHARC Modules.........................................................................................................................39 Reference Section............................................................................................................................................................................................41 AudioProcessing.h Structures...................................................................................................................................................................41 Module Memory Sections.........................................................................................................................................................................44 Summary of Naming Conventions...........................................................................................................................................................45 Inspector Control Types............................................................................................................................................................................47 XML Format................................................................................................................................................................................................50 Index.................................................................................................................................................................................................................51 3 of 51 Preface PURPOSE OF THIS MANUAL The VisualAudio Designer Users’ Guide explains how to use VisualAudio to develop audio processing software for a wide variety of products. The guide describes the graphical interface, provides step-by-step procedures for completing tasks, and contains detailed technical information on how to integrate the generated code into your final product. Intended Audience The primary audience for this manual is a programmer who is familiar with Analog Devices, Inc. processors. This manual assumes that the audience can use the VisualDSP++ development environment to develop, build, and debug Digital Signal Processing (DSP) applications for the SHARC or Blackfin processor. 4 of 51 Custom Audio Modules This document explains how to write an audio processing module for VisualAudio for SHARC processors in the 26x and 36x families, as well as for Blackfin processors in the 53x and 56x families. Audio modules allow audio processing (sometimes called “post-processing”) to be implemented by making use of a number of smaller, self-contained processing blocks. The topics are organized as follows. • “Overview” • “Numerics on SHARC and Blackfin” • “Example 1A – Mono parameter scaling” • “Example 1B – Render function in ASM” • “Scratch Buffers” • “Auxiliary Memory for Module Instances” • “Pointer Aliasing Rules” • “Meta-variables and Expressions” • “Modifying Module Parameters” • “Expression Language Details” • “Modules with Data of Varying Size” • “Modules with Variable Numbers of Pins” • “Other Features of the XML File” • Custom Bypass Functions” • “SHARC SIMD Considerations” • “Adjusting Modules from Other Modules” • “Dynamically Changing a Module’s Render Function” • “Compatibility between Blackfin and SHARC Modules” OVERVIEW This section includes a brief philosophical review of what motivated certain design decisions, a discussion about the quasi-object orientation inherent in the module concept, a description of usage scenarios and a high-level description of the parts of a module. Design Philosophy The module format was designed with the following goals in mind. • Minimal run-time processor footprint • CPU efficiency • Straightforward to write and use Several key features help accomplish these goals. • VisualAudio does as much work as possible at compile and assembly time to enable the production DSP code to be lean, while still providing a flexible environment for creating and deploying modules. • Modules process a block of samples at a time to ensure that the cost of loading and storing state and parameters is incurred only once per block instead of once per sample. • VisualAudio supports interleaved stereo connections between modules to enable a common use of Single-Instruction, Multiple-Data (SIMD) on the SHARC DSP. This signal type is also supported on the Blackfin, primarily for compatibility with system designs originating on SHARCs. 5 of 51 • VisualAudio supports signals at both the audio sampling rate and a lower “control rate.” This allows slowly-changing control signals to use less memory and MIPS. • VisualAudio supports a variety of frequency domain signal types, as well as a user-settable FFT size and hop factor for “overlap-add” and “overlap-save” style processing. • Some of the spirit of object-oriented programming is borrowed, while a lean approach is maintained. Note that C++ is not used. • To keep the CPU usage (MIPS) of a module relatively constant, a module instance should perform roughly the same operations every time it runs. Assume the module’s worst case CPU usage. The exception is when there are clear modes. In this case, the user can plan in advance the combination of module modes that will be in use at a particular time. • In keeping with the goals of near-constant CPU usage and minimal memory usage, parameter calculation (such as filter design) is normally pushed forward to design time, and implemented outside the DSP runtime (for example within VisualAudio Designer). Therefore, modules usually do not contain design or initialization code on the DSP. Instead, module instances are normally initialized and designed via static initialization of their state structures (in code generated by VisualAudio Designer or by the user).1 Module Terminology Each type of processing module is represented by its own module class. These are instantiable; multiple instances of each class may exist at the same time. We use the term module when the distinction between the class and the instance is clear from context. Examples of modules include “Scaler N Smoothed” and “Delay).” The behavior of modules is adjusted via render variables. These are variables that exist on the DSP as part of the module instance structure. In addition, VisualAudio Designer presents high-level interface variables for each module. Interface variables are those exposed via module inspectors within VisualAudio Designer. An interface variable may correspond directly to a render variable. Alternatively, an interface variable may be mapped to a render variable through some function; for example, translating a delay time in milliseconds to a sample delay. Other possibilities include more complicated dependencies, where one or more interface variables touch one or more render variables. Render variables are defined in associated .h files detailing the instance structure of each module; interface variables are defined in associated .xml files. Interface variables are sometimes referred to as high-level variables, while render variables are sometimes referred to as low-level variables. There are three kinds of render variables, differing in restrictions on when they are set: • Constants are typically set only at design time (i.e. their value doesn’t usually change at run time.) • Parameters are typically set at design or tuning time from VisualAudio Designer, or by DSP control code • States can be set by the module’s render function itself, as well as by VisualAudio Designer in tuning mode or by DSP control code. Within VisualAudio Designer, these restrictions are enforced. On the DSP itself, it is up to the user to abide by these guidelines as appropriate. The term render variable is used to distinguish it from a meta-variable, which exists only in VisualAudio Designer’s representation of the module, not on the DSP. Thus, the set of interface variables contains some render variables and some meta-variables. Modules are interconnected via pins. Pins may be designated as either input or output. Either may be of type stereo_pcm, mono_pcm or control. The stereo_pcm and mono_pcm pins are collectively referred to as “audio rate pins,” or simply “audio 1 In stand-alone usage (without VisualAudio Designer) or when modules are implemented in terms of other modules, allocation can be either dynamic or static and initialization DSP code is often included. 6 of 51 pins.” Control rate pins are referred to as “control pins” and are of type control. Frequency domain pins may be of the following types: spectrum_real, spectrum_complex, spectrum_half_real and spectrum_half_complex. These are explained in more detail later. There are two kinds of modules: those that have a fixed number of pins, and those in which the number of input and/or output pins varies from instance to instance. A module class may have outputs, but no inputs, in which case it can be thought of as a signal generator (such as a sine wave generator). Or, it can have inputs, but no outputs, and report its results in a state variable (such as a VU meter). Finally, a module can have neither outputs nor inputs, and can do its work entirely in terms of side effects to itself (modifying its own state) or to other modules (modifying the render variables of other modules). Such a module could be used, for example, in testing other modules, when strictly-repeatable sample-synchronous updates are needed. Render functions must never write to their inputs. To see why this is true, consider a module whose output fans out to several other modules. If the first module wrote to its input, it would corrupt the input to the second module. However, the VisualAudio Designer routing algorithm knows the overall connection between audio modules and may reuse the same patch buffer for the input and output of a module, when it is safe. For more details, see Pointer Aliasing Rules below. Module Usage Scenarios There are two ways that VisualAudio modules can be used: • In a drag-and-drop fashion from VisualAudio Designer - Memory allocation, parameter setting and calling of the render function are handled automatically. • As C-callable functions in a stand-alone library - Memory allocation, parameter setting and calling of the render function are all handled by the user’s C or assembly code. Even if a module is used in drag-and-drop fashion, its render variables may be modified in the DSP program’s control code (sometimes referred to as “user control code.”) Similarly, a module used in a drag-and-drop fashion may include, in its implementation, a render function that calls other render functions using the stand-alone style. This document contains information on developing modules that may be used in either style of usage. For more information on usage, see the document VisualAudio Module Library Usage Guide. For more information on the particular modules supplied by VisualAudio, see VisualAudio Module Library Reference for Blackfin and VisualAudio Module Library Reference for SHARC. Module Modes When used within a layout generated by VisualAudio Designer1, a module may be in one of four modes. These can be set at runtime with the following function: AMFSetModuleStatus(AMF_Module *module, AMF_ModuleStatus status) The possible status values and their meanings are given below. • AMFModuleStatus_ACTIVE. The module processes its inputs and writes its outputs via its render function each time it is run. This is the default mode. Note that a module may have several alternative render functions, but one must be specified as the default. • AMFModuleStatus_INACTIVE. The module is not run. This implies that its outputs are not written, leaving their contents undefined. • AMFModuleStatus_MUTED. The module's outputs are zeroed each time it is run. This behavior is provided automatically. You need not write any code to implement this mode. 1 More specifically, when used with the VisualAudio Layout Support library. 7 of 51 • AMFModuleStatus_BYPASSED. The module performs the bypass function, which means that its input(s) are copied to its output(s) each time it is run. The default algorithm copies audio inputs to audio outputs, copies signal inputs to signal outputs, and mutes unused outputs. Where there is a mono/stereo mismatch, stereo is converted to mono by adding the channels and dividing by two; mono is converted to stereo by duplicating the channel. Alternatively, the module designer may provide a custom bypass function. For more information, see How to Write a Custom Bypass Function below. The default bypass algorithm copies the Nth input pin of a given type to the Nth output pin of the same type. For example, the 3rd control pin input is copied to the 3rd control pin output. If there are more output pins than input pins, the remainder are muted. Note that for the purposes of bypass, stereo and mono pins are considered the same type. If a mono input matches a stereo output, the mono input is duplicated on both channels. If a stereo input matches a mono output, the stereo channels are added and divided by 2. Parts of a Module A module consists of these parts: • A header (.h) file that defines the run-time interface to the module, including the instance structure typedef. The name of this file must be the same as the module name with .h (for example, AMF_Scaler.h). • The module’s run-time DSP code, in source or binary form (e.g., to protect any intellectual property). The VisualAudio Module Library is delivered in binary form as a VisualDSP++ .dlb file, and the source is also included. If delivered in source form, the module must contain the following two parts: • The module’s render function, which implements the module’s primary function • The module’s class object, which describes the module to the run-time system • A .xml file that describes the module to VisualAudio Designer in detail. This file is not required if the module is never used with VisualAudio Designer. The name of this file must be the same as the module name, with .xml appended (for example, AMF_Scaler.xml where “AMF” stands for Audio Module Format). The .xml file includes information about what files constitute the module’s run-time and header files, as well as information about the module’s parameters, and may also include simple design formulas. How to Add a Module to VisualAudio Designer To make a custom SHARC module available to VisualAudio Designer, create a directory (we’ll call it xxx) and put the XML, include, source files and object files1 in sub-directories. For the SHARC, the subdirectories should be: • XML files in xxx\SHARC\XML\ • Header files in xxx\SHARC\Include\ • Source files in xxx\SHARC\Source\ • Object files in xxx\SHARC\Lib For the Blackfin, they should be: • XML files in xxx\Blackfin\XML\ • Header files in xxx\Blackfin\Include\ • Source files in xxx\Blackfin\Source\ • Object files in xxx\Blackfin\Lib Where xxx is your Modules directory. You must then add your Modules directory to the list of directories searched by VisualAudio Designer. See the VisualAudio Designer User's Guide for details. 1 Third parties can protect their IP by delivering it as a library (a .dlb). Alternatively, they can deliver it is as a pre-compiled or pre-assembled object file (a .doj). 8 of 51 You must add your custom module source files to the VisualDSP++ project (.dpj) file for your platform. In contrast, when a module is included in object form (.dlb or .doj), it is automatically added to the linker list via the VALinkerCmds.txt file. NUMERICS ON THE BLACKFIN AND SHARC The primary difference between Blackfin and SHARC modules is the use of floating point on the SHARC. On the Blackfin, floating point is not available in hardware; hence Blackfin modules typically operate in fixed point. The basic VisualAudio signal type on the Blackfin is fract32, a 32-bit 1.31 format fraction. The basic VisualAudio signal type on the SHARC is a float, a 32-bit floating point number. To ease the task of moving between SHARC and Blackfin, VisualAudio defines a type AMF_Signal, which is fract32 for Blackfin and float for SHARC. Most SHARC modules use floating point internally. However, extended precision SHARC modules may use fixed point internally. Most Blackfin modules use fixed point internally. A number of conventions have been established for fixed-point processing on the Blackfin. We recommend that custom modules obey these conventions for maximum compatibility: The default format for fixed point coefficients is 1.31. Coefficients which perform a “volume scaling” can be 16 bits (typically 1.15 format), so that faster 16x32 multiplication can be used (as opposed to 32x32), since a volume-like scale tends not to need to be represented with an extremely high precision. Smoothing of 16-bit coefficients may need to be performed at 32 bits (to allow the smoothing to move at very slow smoothing rates), but the top 16 bits can still be used for doing the volume scaling cheaply. Headroom in signals is assumed to be managed by the layout creator, not by the module or by VisualAudio. Therefore, except where noted, a Blackfin module assumes 1.31 input and output signals, and for compatibility a SHARC module assumes signals where 1.0f corresponds to maximum amplitude (though clipping to +/- 1.0 is only implemented at the output). Saturating arithmetic is used in fixed point modules. In fixed point modules, multiplications implemented to “31-bit” precision (i.e. discarding the low order product as a speed optimization) may be used as a satisfactory substitute for full 32x32 multiplications. 16 bit types (fract16 and int16 ) as module variables are not supported on the SHARC in VisualAudio. The module implementer is responsible for creating correct alignment in the module state structure, if necessary (via padding and/or ordering). This is an issue only with Blackfin modules. The structures allocated by VisualAudio Designer can be assumed to be aligned to 32-bit boundaries. EXAMPLE 1A – MONO PARAMETRIC SCALING The following example shows a parametric scaling of a mono signal, for both SHARC and Blackfin versions of VisualAudio Example 1A Header File: AMF_Scaler.h The example module’s header file is shown below, for the SHARC or Blackfin version of VisualAudio: /***** Begin AMF_Scaler.h *******/ // Include header file with base class definitions: #include "AudioProcessing.h" // Instance structure typedef 9 of 51 typedef struct { AMF_Module b; // Parameters AMF_Signal amplitude; } AMF_Scaler; // Class object declaration extern const AMF_ModuleClass AMFClassScaler; /**** End AMF_Scaler.h *****/ Notice that the instance structure begins with an embedded struct of type AMF_Module. All module instance structures must begin in this manner (this allows any module’s struct to be interpreted as an AMF_Module, hence implementing a form of inheritance). This struct is followed by a single render variable, amplitude. The structure for the Blackfin and SHARC versions of the module are identical, except for the definition of AMF_Signal as fract32 instead of float in AudioProcessing.h. Example 1A Code File: AMF_Scaler.c The example module’s C code file is AMF_Scaler.c. The first half of the C file for the SHARC version of the module is listed below and analyzed in detail, with comparisons to the Blackfin version as necessary. /****** Begin AMF_Scaler.c *********/ #include "AMF_Scaler.h" // The module's header file #pragma optimize_for_speed // VisualDSP++ directive SEG_MOD_FAST_CODE void AMF_Scaler_Render( AMF_Scaler *restrict instance, AMF_Signal * restrict * buffers, int tickSize) { int i; AMF_Signal *in = buffers[0]; AMF_Signal *out = buffers[1]; AMF_Signal amplitude = instance->amplitude; #pragma SIMD_for for (i=0; iamplitude; #pragma SIMD_for for (i=0; iamplitude; for (i=0; i tag with value 2. To make it easy to supply values for the type vector, the following macros are supplied: #define AMF_StereoPin(whichPin) \ (AMFPinType_STEREO<<(whichPin*4)) #define AMF_ControlPin(whichPin) \ (AMFPinType_CONTROL<<(whichPin*4)) #define AMF_MonoPin(whichPin) (0) #define AMF_SpectrumRealPin(whichPin) \ (AMFPinType_SPECTRUM_REAL<<(whichPin*4)) #define AMF_SpectrumComplexPin(whichPin) \ (AMFPinType_SPECTRUM_COMPLEX<<(whichPin*4)) #define AMF_SpectrumHalfRealPin(whichPin) \ (AMFPinType_SPECTRUM_HALF_REAL<<(whichPin*4)) #define AMF_SpectrumHalfComplexPin(whichPin) \ (AMFPinType_SPECTRUM_HALF_COMPLEX<<(whichPin*4)) Type descriptors can then be assembled by bitwise OR’ing of these macros. Note that the whichPin argument is zero-based. For example, if a module has one mono input followed by one stereo input, its input type designator could be written as: (AMF_MonoPin(0) | AMF_StereoPin(1)) Alternatively, it could be written directly as 0x10. If there are more than eight pins, then the high order nibble is assumed to be sticky and applies to all pins beyond eight. However, there are situations where this convention is inadequate, such as when a pin greater than the 8th has a type differing from the 8th. For these situations, an indirect form is available as follows: 13 of 51 If the AMF_ModuleClass flags field includes the bit AMFModuleClassFlag_INDIRECT_INPUT_PIN_TYPE, then the input type descriptor is actually a pointer to an array of sufficient length to support bit vectors for all input pins. Similarly, if the flags include the bit AMFModuleClassFlag_INDIRECT_OUTPUT_PIN_TYPE, then the output type descriptor is actually a pointer to an array of sufficient length to support bit vectors for all input pins. In modules with variable number of pins (described in a later section of this document), the input and output type descriptors are in the instance, rather than the class. Example 1A XML File: AMF_Scaler.xml The .xml file describes the module to VisualAudio Designer. In this discussion, we assume a minimal familiarity with XML. Please note that all module xml element type attributes (i.e. type = “string”, type = “float” etc.) are optional as of VisualAudio 1.6 and therefore, are not shown in the examples below. When creating a custom module, we recommend copying the XML file from an existing module, renaming the XML file, and modifying it. At the outermost level, the XML file looks like this: . . . It begins by telling the XML parser where to find the VisualAudio Designer schema, which is used to validate the file.1 Validating the file ensures that it has all the information needed by VisualAudio Designer, that it is structured correctly, that the fields are listed in the proper order, and that it contains legal values for the required fields. The actual module definition is inside the body of the tag, which includes the information detailed below. Module Fields A module has several different self-description tags • The