Anda di halaman 1dari 86

ABR Haskell Libraries Implementation

Andrew Rock
School of Information and Communication Technology
Griffith University
Nathan, Queensland, 4111, Australia
a.rock@griffith.edu.au

Abstract
This document lists and describes the libraries developed for and
common to the various systems I have developed in Haskell1 , including the implementation of all functions.

Contents
1 Introduction

2 Installation

3 Util.Args
3.1 Maintenance notes . . . . .
3.2 Language tweaks . . . . . .
3.3 Data types . . . . . . . . .
3.4 Empty options . . . . . . .
3.5 Option detection . . . . . .
3.6 Adding and deleting options
3.7 Looking up options . . . . .
3.8 Filename globbing . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

5
5
5
5
5
5
5
6
6

4 Util.Errors
4.1 Maintenance notes . . . . . . . . . . . . .
4.2 Warning and error data type . . . . . . .
4.3 Warning and error category type class . .
4.4 Data types . . . . . . . . . . . . . . . . .
4.5 Returning successfully, failing or warning
4.6 Instances . . . . . . . . . . . . . . . . . .
4.6.1 Functor . . . . . . . . . . . . . . .
4.6.2 Applicative . . . . . . . . . . . . .
4.6.3 Monad . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

6
6
6
6
6
6
6
6
7
7

5 Util.Open
5.1 Maintenance notes
5.2 System specifics . .
5.3 Tests . . . . . . . .
5.4 Opening a URL . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

7 Util.Time
7.1 Maintenance notes . . . . . . . . . . .
7.2 Data types . . . . . . . . . . . . . . .
7.3 Time conversions . . . . . . . . . . . .
7.4 Getting the current time . . . . . . . .
7.5 Format strings . . . . . . . . . . . . .
7.6 Formatting times . . . . . . . . . . . .
7.7 Formated current time . . . . . . . . .
7.8 Formated file modification time . . . .
7.9 Overloading for old/new time systems
7.10 Instances . . . . . . . . . . . . . . . .
7.10.1 LegacyTimes . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

7
8
8
8
8
8
8
8
8
8
9
9

8 Control.Check
8.1 Maintenance notes
8.2 Data type . . . . .
8.3 Sequencing checks
8.4 Parallel checks . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

9
9
9
9
9

9 Control.List
9.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
9.2 Backwards map . . . . . . . . . . . . . . . . . . . . .

9
9
9

10 Control.Map
10.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
10.2 lookupGuard . . . . . . . . . . . . . . . . . . . . . .

9
9
10

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

10
10
10
10
10
11
11
12

.
.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

7
7
7
7
7

11 Data.List
11.1 Maintenance notes . . .
11.2 Language tweaks . . . .
11.3 Sorting . . . . . . . . . .
11.4 Combinatorics . . . . .
11.5 Bag-like operations . . .
11.6 Set-like operations . . .
11.7 Subsequence operations

6 Util.Pos
6.1 Maintenance notes . . . .
6.2 Positions in a source . . .
6.3 Overloaded projector . . .
6.3.1 Container intances
6.4 Relative positions . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

7
7
7
7
7
7

12 Data.NameTable
12.1 Maintenance notes . .
12.2 Data types . . . . . .
12.3 Creating a name table
12.4 Looking up by name .
12.5 Creating a name array

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

12
13
13
13
13
13

13 Data.Supply
13.1 Maintenance notes . .
13.2 Data types . . . . . .
13.3 Creating a supply . . .
13.4 Extracting values from

.
.
.
a

. . . .
. . . .
. . . .
supply

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

13
13
13
13
13

14 Debug.Array
14.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
14.2 Functions . . . . . . . . . . . . . . . . . . . . . . . .

13
13
13

15 Debug.IArray
15.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
15.2 Functions . . . . . . . . . . . . . . . . . . . . . . . .

14
14
14

.
.
.
.

.
.
.
.

.
.
.
.

1 ABRHLibs a personal library of Haskell modules Copyright


(C) 2007-2013 Andrew Rock
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 021101301 USA

April 3, 2015

16 Parser
16.1 Maintenance notes . . . . . . . .
16.2 Error messages . . . . . . . . . .
16.3 Results . . . . . . . . . . . . . . .
16.4 Analysers . . . . . . . . . . . . .
16.5 Elementary analysers . . . . . . .
16.6 Elementary analyser combinators
16.7 Analyser result modifiers . . . . .
16.8 More analyser combinators . . .
16.9 Lexers . . . . . . . . . . . . . . .
16.10Elementary lexers . . . . . . . . .
16.11Special combinators for lexers . .
16.12Frequently used lexers . . . . . .
16.13Parsing . . . . . . . . . . . . . .
16.14Elementary parsers . . . . . . . .
16.15Parser result modifiers . . . . . .
16.16Error reporting . . . . . . . . . .
16.17Instance declarations . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

14
14
14
14
14
14
15
15
15
16
16
16
17
17
17
17
17
17

17 Parser.Lexers
17.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
17.2 Frequently used lexers . . . . . . . . . . . . . . . . .

17
17
17

18 Parser.Checks
18.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
18.2 Easy lexer and parser sequencing . . . . . . . . . . .

19
19
19

19 Parser.Predicates
19.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
19.2 isNumber predicates . . . . . . . . . . . . . . . . . .

19
19
19

20 Text.Configs
20.1 Maintenance notes . . . . . . . . . . . . . .
20.2 Language tweaks . . . . . . . . . . . . . . .
20.3 Data types . . . . . . . . . . . . . . . . . .
20.4 Lexer . . . . . . . . . . . . . . . . . . . . .
20.5 Parser . . . . . . . . . . . . . . . . . . . . .
20.6 Showing . . . . . . . . . . . . . . . . . . . .
20.7 Reading . . . . . . . . . . . . . . . . . . . .
20.8 Accessing . . . . . . . . . . . . . . . . . . .
20.8.1 configPaths . . . . . . . . . . . . . .
20.8.2 Lookup functions . . . . . . . . . . .
20.9 Templates . . . . . . . . . . . . . . . . . . .
20.9.1 Simple Template Markup Language
20.9.2 Populating templates . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

19
19
19
19
20
20
21
21
21
21
21
22
22
22

21 Text.JSON
21.1 Maintenance notes .
21.2 Language tweaks . .
21.3 Basic data types . .
21.4 Escaping/descaping .
21.5 Construction . . . .
21.6 Lexer . . . . . . . .
21.7 Parser . . . . . . . .
21.8 Interrogation . . . .
21.9 Instances . . . . . .
21.9.1 Showing . . .
21.9.2 Interrogation

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

23
23
23
23
24
24
24
25
26
26
26
27

22 Text.CSV
22.1 Maintenance notes .
22.2 Basic data types . .
22.3 Parser . . . . . . . .
22.4 Interrogation . . . .
22.5 Instances . . . . . .
22.5.1 Showing . . .
22.5.2 Interrogation

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

27
27
27
27
27
27
27
27

23 Text.Markup
23.1 Maintenance notes . . . . . .
23.2 Making text safe for HTML .
23.3 Making text safe for LaTeX .
23.4 Converting LaTeX to HTML

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

27
27
27
28
28

April 3, 2015

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

24 Text.String
24.1 Maintenance notes . . . . . . . . .
24.2 Word wrapping . . . . . . . . . . .
24.3 Justification . . . . . . . . . . . . .
24.4 Tables with justified columns . . .
24.5 Fields . . . . . . . . . . . . . . . .
24.6 Whitespace . . . . . . . . . . . . .
24.7 Pattern matching and substitution
24.8 Names . . . . . . . . . . . . . . . .
24.9 Path catenation operators . . . . .
24.10Simple String Delimitation . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

29
29
29
30
30
30
30
31
31
31
31

25 Text.Showing
25.1 Maintenance notes .
25.2 Language tweaks . .
25.3 Adding Delimiters .
25.4 Controlling Precision

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

31
32
32
32
32

26 Logic.Kinds
26.1 Maintenance notes .
26.2 Data type . . . . . .
26.3 Unification . . . . .
26.4 Kind inference . . .
26.5 Instance declarations
26.5.1 Showing . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

32
32
32
32
32
33
33

27 Logic.Constants
27.1 Maintenance notes . . . . .
27.2 Data types . . . . . . . . .
27.3 Parsers . . . . . . . . . . . .
27.4 Collecting constants . . . .
27.5 Instance declarations . . . .
27.5.1 Comparing . . . . .
27.5.2 Positions . . . . . .
27.5.3 Showing . . . . . . .
27.5.4 Collecting constants
27.5.5 Kind inference . . .
27.5.6 DeepSeq . . . . . . .

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

33
33
33
33
33
33
33
33
33
33
34
34

28 Logic.Variables
28.1 Maintenance notes . . . . .
28.2 Data type . . . . . . . . . .
28.3 Parsers . . . . . . . . . . . .
28.4 Collecting variables . . . . .
28.5 Grounding . . . . . . . . . .
28.6 Instance declarations . . . .
28.6.1 Comparing . . . . .
28.6.2 Positions . . . . . .
28.6.3 Showing . . . . . . .
28.6.4 Collecting variables .
28.6.5 Grounding . . . . .
28.6.6 Kind inference . . .
28.6.7 DeepSeq . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

34
34
34
34
34
34
34
34
34
34
34
34
34
34

29 Logic.Arguments
29.1 Maintenance notes . . . . .
29.2 Data type . . . . . . . . . .
29.3 Parsers . . . . . . . . . . . .
29.4 Instance declarations . . . .
29.4.1 Positions . . . . . .
29.4.2 Comparing . . . . .
29.4.3 Showing . . . . . . .
29.4.4 Collecting constants
29.4.5 Collecting variables .
29.4.6 Grounding . . . . .
29.4.7 Kind inference . . .
29.4.8 DeepSeq . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.

35
35
35
35
35
35
35
35
35
35
35
35
36

30 Logic.Atoms
30.1 Maintenance notes . . . . .
30.2 Data type . . . . . . . . . .
30.3 Parsers . . . . . . . . . . . .
30.4 Collecting atoms . . . . . .
30.5 Instance declarations . . . .
30.5.1 Comparing . . . . .
30.5.2 Positions . . . . . .
30.5.3 Showing . . . . . . .
30.5.4 Collecting Atoms . .
30.5.5 Collecting constants

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

36
36
36
36
36
36
36
36
36
37
37

30.5.6 Collecting variables . . . . . . . . . . . . . . .


30.5.7 Grounding . . . . . . . . . . . . . . . . . . .
30.5.8 DeepSeq . . . . . . . . . . . . . . . . . . . . .
31 Logic.Literals
31.1 Maintenance notes . . . . .
31.2 Data type . . . . . . . . . .
31.3 Parser . . . . . . . . . . . .
31.4 Negation . . . . . . . . . . .
31.5 Instance declarations . . . .
31.5.1 Comparing . . . . .
31.5.2 Positions . . . . . .
31.5.3 Showing . . . . . . .
31.5.4 Negation . . . . . .
31.5.5 Collecting Atoms . .
31.5.6 Collecting constants
31.5.7 Collecting variables .
31.5.8 Grounding . . . . .
31.5.9 DeepSeq . . . . . . .

37
37
37

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

37
37
37
37
37
37
37
37
37
38
38
38
38
38
38

32 Logic.QuineMcClusky
32.1 Maintenance notes . . . . . .
32.2 Data types . . . . . . . . . .
32.3 Conversion from lists to trees
32.4 Conversion from trees to lists
32.5 Combining QMTrees . . . . .
32.6 Simplification . . . . . . . . .
32.7 Instance declarations . . . . .
32.7.1 DeepSeq . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

38
38
38
38
38
39
39
40
40

33 Graphics.Geometry
33.1 Maintenance notes . . .
33.2 Data types . . . . . . .
33.3 Geometric computations
33.4 Instance declarations . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

40
40
40
41
41

34 Graphics.EPS
34.1 Maintenance notes . . . . . . . .
34.2 Data types . . . . . . . . . . . .
34.3 Finalizing to EPS . . . . . . . . .
34.4 Drawing in BPS . . . . . . . . .
34.5 Merging BPS components . . . .
34.6 Drawing text . . . . . . . . . . .
34.6.1 Switching fonts efficiently
34.6.2 Font metrics . . . . . . .
34.6.3 Font tags . . . . . . . . .
34.6.4 Font strings . . . . . . . .
34.6.5 Font blocks . . . . . . . .
34.6.6 Text encoding . . . . . . .
34.7 Conveniences . . . . . . . . . . .
34.8 Instance declarations . . . . . . .
34.8.1 EPSDrawable . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

41
42
42
42
42
42
42
42
42
45
46
46
46
46
47
47

35 File.Lock
35.1 Maintenance notes . . .
35.2 Basic lock operations . .
35.3 Multiple file operations .
35.4 Guards . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

47
47
47
47
48

36 File.Versions
36.1 Maintenance notes . . . . . . . . .
36.2 Read the latest version . . . . . . .
36.3 Date of the latest version . . . . .
36.4 Write the next version . . . . . . .
36.5 Purge old versions . . . . . . . . .
36.6 Remove all versions . . . . . . . . .
36.7 Get all versions . . . . . . . . . . .
36.8 Read and write file bottlenecks . .
36.9 Creating and removing directories
36.10Private routines . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

48
48
48
48
48
48
48
49
49
49
49

April 3, 2015

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

37 CGI
37.1 Maintenance notes . . . . . . . . . .
37.2 Mime header . . . . . . . . . . . . .
37.3 Document type . . . . . . . . . . . .
37.4 Special character encoding . . . . . .
37.5 HTML elements (generic) . . . . . .
37.6 HTML elements (specific shortcuts)
37.7 Standards . . . . . . . . . . . . . . .
37.8 Bailing out . . . . . . . . . . . . . .
37.9 CGI inputs . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

49
50
50
50
50
50
50
51
51
51

38 Daytime
38.1 Maintenance notes .
38.2 Language tweaks . .
38.3 Data types . . . . .
38.4 Lexing . . . . . . . .
38.5 Parsing . . . . . . .
38.6 Instance declarations
38.6.1 Showing . . .
38.6.2 Arithmetic .
38.7 Weekday methods .
38.8 Daytime methods . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

52
52
52
52
52
53
54
54
54
54
54

39 HaskellLexer
39.1 Maintenance notes . . .
39.2 Language tweaks . . . .
39.3 Handling literate scripts
39.4 Lexing scripts . . . . . .
39.5 Handling the offside rule
39.6 Diagnostics . . . . . . .
39.7 Poor mans parsing . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

54
54
54
54
55
61
62
62

40 Playing Cards
40.1 Maintenance notes .
40.2 Data types . . . . .
40.3 Creating decks . . .
40.4 Instance declarations
40.5 Tests . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

63
64
64
64
64
64

. . . .
. . . .
. . . .
. . . .
cards
. . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

64
64
64
65
65
66
66

42 SendMail
42.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
42.2 Function . . . . . . . . . . . . . . . . . . . . . . . . .

66
66
66

43 (Experimental) Data.Graph
43.1 Maintenance notes . . . . .
43.2 Graph abstract data type .
43.3 Sparse graph type . . . . .
43.4 Example sparse graphs . . .
43.5 Graph Operations . . . . .
43.6 Reachability . . . . . . . . .
43.7 Cycles detection . . . . . .
43.8 Dense graph type . . . . . .
43.9 Imported Stuff . . . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

41 Poker
41.1 Maintenance notes . . .
41.2 Presorting and grouping
41.3 Categorisation of hands
41.4 Comparison of hands . .
41.5 Hands with more than 5
41.6 Tests . . . . . . . . . . .

44 (Experimental) MySQL C
44.1 Maintenance notes . . .
44.2 API Data types . . . . .
44.2.1 Basics . . . . . .
44.2.2 Connections . . .
44.2.3 Results . . . . .
44.2.4 Rows . . . . . . .
44.2.5 Fields . . . . . .
44.2.6 Options . . . . .
44.3 API Functions . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

66
66
66
66
67
67
67
67
67
67

API Binding
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

69
69
69
69
69
69
69
69
69
69

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

45 (Experimental) MySQL Haskell API


45.1 Maintenance notes . . . . . . . . . .
45.2 Data types . . . . . . . . . . . . . .
45.2.1 Connections . . . . . . . . . .
45.3 Functions . . . . . . . . . . . . . . .
45.3.1 Establishing a connection . .
45.3.2 Closing a connection . . . . .
45.3.3 Issuing a query . . . . . . . .
45.3.4 Fetching query results . . . .
45.4 Utilities . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

72
72
72
72
72
72
72
72
72
73

46 (Experimental) Database.Relational
46.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
46.2 Definitions and data types . . . . . . . . . . . . . . .
46.3 Relational algebra . . . . . . . . . . . . . . . . . . .
46.3.1 Projection of a tuple on a relation schema . .
46.3.2 Projection of a relation on a relation schema
46.3.3 Natural join . . . . . . . . . . . . . . . . . . .
46.3.4 Union . . . . . . . . . . . . . . . . . . . . . .
46.3.5 Difference . . . . . . . . . . . . . . . . . . . .
46.3.6 Selection . . . . . . . . . . . . . . . . . . . . .
46.3.7 Renaming . . . . . . . . . . . . . . . . . . . .
46.4 Datum operations . . . . . . . . . . . . . . . . . . .

73
73
73
73
73
73
73
74
74
74
74
74

47 (Deprecated) Data.BSTree
47.1 Maintenance notes . . . .
47.2 Language tweaks . . . . .
47.3 BSTree type . . . . . . . .
47.4 BSTree operations . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

References

81

Index

81

Introduction

This document lists and describes the libraries developed for and
common to the various systems I have developed in Haskell. These
include ENTRE (a toy functional language interpreter), Virgil (a
system for managing groups of students within a course), Deimos
(an implementation of Defeasible Logic), and Phobos (an implementation of Plausible Logic).
Each section in this document, except this introduction, is a complete listing of one module from the library.
Haskell code is presented in typewriter font. Some text in
typewriter font is not Haskell and is boxed to differentiate it from
Haskell. The source code for the Haskell modules have been written
in the literate style, and the following sections have been produced
directly from the Haskell+LATEX source code. The symbol $ appears is command examples to represent the command line prompt.
Milti-line commands are continued with the UNIX escape character,
\.
The SimpleLit tool has been used to separate interface and implementations into separate LATEX documents.
Please let me know of any defects or possible improvements that
you spot. Some modules are works-in-progress.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

74
74
74
74
75

48 (Deprecated) Data.HashTables
48.1 Maintenance notes . . . . . . .
48.2 Data types . . . . . . . . . . .
48.3 Creating a new hash table . . .
48.4 Updating an existing hash table
48.5 Looking up in a hash table . .
48.6 Dumping a hash table . . . . .
48.7 Test harness . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

76
76
76
76
77
77
77
77

49 (Deprecated) Data.Queue
49.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
49.2 Data type . . . . . . . . . . . . . . . . . . . . . . . .
49.3 Operations . . . . . . . . . . . . . . . . . . . . . . .

77
77
77
77

50 (Deprecated) Data.SparseSet
50.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
50.2 Data type . . . . . . . . . . . . . . . . . . . . . . . .
50.3 Operations . . . . . . . . . . . . . . . . . . . . . . .

78
78
78
78

51 (Deprecated) Data.Set
51.1 Maintenance notes .
51.2 Data type . . . . . .
51.3 Operations . . . . .
51.4 Instances . . . . . .
51.4.1 Ord . . . . .
51.4.2 Showing . . .
51.4.3 DeepSeq . . .

To compile the Haskell libraries, ghc is required. To typeset the


documentation, the tools bibtex and pdflatex are required.
To compile all of the libraries (except those requiring MySQL),
use

.
.
.
.

.
.
.
.

Installation

This library is packaged for distribution in the file ABRHLibs.zip,


available from
http://http://www.ict.griffith.edu.au/arock/haskell/
This file contains a Makefile, the library documentation (as two
PDF files), and the source code (as a collection of literate sources
with the extension .lit and the .lhs Haskell sources derived from
them).
To extract the .tex and .lhs files from the .lit sources, the
SimpleLit tool is required, and is also availble from the link above.
Some modules include parsers, documented with EBNF specifications and syntax diagrams derived from them. The Syntrax tool
that does this conversion is also available from the link above.
Before compiling, change your current working directory to
ABRHLibs/src.
$ cd ABRHLibs/src

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

78
78
78
78
80
80
80
80

52 (Deprecated) DeepSeq
52.1 Maintenance notes . . .
52.2 Class Definition . . . . .
52.3 Infix operator . . . . . .
52.4 Instance Declarations . .
52.4.1 Simple instances
52.4.2 Tuple instances .
52.4.3 List instance . .
52.4.4 Maybe instance .
52.4.5 Either instance .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

80
80
80
80
80
80
80
80
80
80

$ make doc

.
.
.
.
.
.
.
.
.

53 (Deprecated) DeepSeq.BStree
53.1 Maintenance notes . . . . . . . . . . . . . . . . . . .
53.2 Instance declaration . . . . . . . . . . . . . . . . . .

80
80
80

$ make clean

54 (Deprecated) Logic.Qualification
54.1 Maintenance notes . . . . . . . .
54.2 Qualifiable class . . . . . . . . . .
54.3 Instance declarations . . . . . . .
54.3.1 Qualification . . . . . . .

81
81
81
81
81

April 3, 2015

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

$ make objects
To typeset the documentation, use

To build everything listed above, use


$ make all
or just
$ make
To delete intermediate files, use

To delete those and the objects, interfaces and intermediate LATEX


and Haskell sources, use
$ make CLEAN
To rebuild the file ABRHLibs.zip, use
$ make distribute

Util.Args

Module ABR.Util.Args provides a way to pick apart the meanings


of command line arguments.
{-# LANGUAGE CPP #-}
module ABR.Util.Args (
OptSpec(..), OptVal(..), Options, findOpts,
assertFlagPlus, assertFlagMinus, deleteFlag,
insertParam, deleteParam, emptyOptions,
lookupFlag, lookupParam, lookupQueue
# if __GLASGOW_HASKELL__ < 705
-- legacy for hobbit
# else
, glob
# endif
) where
import
import
import
import

qualified
qualified
qualified
qualified

Data.List as L
Data.Sequence as S
Data.Foldable as F
Data.Map as M

Hackage package Glob:


# if __GLASGOW_HASKELL__ < 705
-- legacy for hobbit
# else
import qualified System.FilePath.Glob as G
# endif

3.1

Maintenance notes

Reviewed 2015-02-12: Made globbing conditional.


Reviewed 2015-02-12: Added filename globbing.
Reviewed 2015-02-02: Passed hlint.
Reviewed 2014-05-29: Made all the -Walls go away.
Reviewed 2013-11-06.
Reviewed 2012-11-24: Moved into ABR.Util.
Reviewed 2012-11-01: Keep this module but make it use the data
types in the CHC libs instead of my own data types done.

3.2

Language tweaks

undefined :: String -> a


undefined label = error $
"Intentional undefined at module ABR.Util.Args, " ++
" with label \"" ++ label ++ "\"."

3.3

Data types

An OptSpec is used to specify the types of option expected.


data OptSpec =
FlagS String | ParamS String | QueueS String
deriving (Eq, Show)
An option is one of:
a flag to be set or unset. Specify with FlagS name. Users
set or unset with +name or -name respectively.
a parameter with a value. Specify with ParamS name. Users
provide values with -name value.
a parameter that can have multiple values. The order of the
multiple values might be significant. In this case a queue of
strings should be returned. Specify with QueueS name. Users
provide values with -name value 1 -name value 2 . . ..
An OptVal is used to indicate presence of a command line option. Flags might be FlagPlus or FlagMinus . Parameters will
either return the ParamValue value or an indication that the
value was missing, ParamMissingValue . Queue parameters return
ParamQueue queue. Missing values for queue parameters might
yield an empty queue.
data OptVal =
FlagPlus | FlagMinus |
ParamValue String | ParamMissingValue |
ParamQueue (S.Seq String)
deriving Show

April 3, 2015

An Options is used to map from an option name to its value(s).


type Options = M.Map String OptVal

3.4

Empty options

emptyOptions is an empty Options.


emptyOptions :: Options
emptyOptions = M.empty

3.5

Option detection

findOpts optSpecs args returns (options, leftovers), where:


optSpecs is a list of option specifications; args is a list of command
line arguments; options is a mapping from the option names to the
values found; and leftovers is a list of any unconsumed arguments,
typically file names.
findOpts :: [OptSpec] -> [String] -> (Options, [String])
findOpts specs = fo
where
rep, enq :: Options -> String -> OptVal -> Options
rep t cs v = M.insertWith const cs v t
enq t cs v = M.insertWith enq cs v t
enq :: OptVal -> OptVal -> OptVal
enq (ParamQueue q) (ParamQueue q) =
ParamQueue (q S.>< q)
enq _ _ = undefined "enq"
fo :: [String] -> (Options, [String])
fo args = case args of
[] -> (M.empty, [])
((c:cs):css)
| c == + && FlagS cs elem specs ->
let (t, css) = fo css
in (rep t cs FlagPlus, css)
| c == - && FlagS cs elem specs ->
let (t, css) = fo css
in (rep t cs FlagMinus, css)
| c == - && ParamS cs elem specs ->
case css of
[] ->
(rep M.empty cs ParamMissingValue, [])
(cs:css) ->
let (t, css) = fo css
in (rep t cs (ParamValue cs), css)
| c == - && QueueS cs elem specs ->
case css of
[] ->
(enq M.empty cs
(ParamQueue S.empty), [])
(cs:css) ->
let (t, css) = fo css
in (enq t cs (ParamQueue
(S.singleton cs)), css)
| otherwise ->
let (t, css) = fo css
in (t, (c:cs):css)
([]:_) -> undefined "fo"

3.6

Adding and deleting options

Flags can be asserted positive or negative or deleted, with


assertFlagPlus , assertFlagMinus , and deleteFlag , respectively.
assertFlagPlus, assertFlagMinus, deleteFlag ::
String -> Options -> Options
assertFlagPlus name = M.insert name FlagPlus
assertFlagMinus name = M.insert name FlagMinus
deleteFlag
= M.delete
Params can be inserted or deleted, with
deleteParam , respectively.

insertParam

and

insertParam :: String -> String -> Options -> Options


insertParam name value = M.insert name (ParamValue value)
deleteParam :: String -> Options -> Options
deleteParam = M.delete

3.7

Looking up options

lookupFlag name options def returns the value (FlagPlus means


True) stored for the nameed flag in options or def if it has not been
properly specified.
lookupFlag :: String -> Options -> Bool -> Bool
lookupFlag name options def =
case M.lookup name options of
Just FlagPlus -> True
Just FlagMinus -> False
_
-> def
lookupParam name options def returns the value stored for the
nameed parameter in options or def if it has not been properly
specified.

4.1

Maintenance notes

Reviewed 2015-02-02. Passed hlint.


Reviewed 2014-06-10. Added Functor and Applicative instances
for WEResult, written by George Wilson.
Reviewed 2014-05-29. Made all the -Walls go away.
Reviewed 2013-11-06.
Reviewed 2012-11-23: Adapted and generalized from MaSH.Errors.

4.2

Warning and error data type

A WEMessage always has:


a category ( weCat );
the text of the message ( weMsg );
and may have:
a position wePos .
The category is a type provided by the application.
data WEMessage
weCat ::
wePos ::
weMsg ::
}

cat = WEMessage {
cat,
Maybe Pos,
String

lookupParam :: String -> Options -> String -> String

4.3

Warning and error category type


class

lookupParam name options def =


case M.lookup name options of
Just (ParamValue v) -> v
_
-> def

Class ErrorCategory includes all types that categorise errors or


warnings.
class ErrorCategory cat where

lookupQueue name options returns the list stored for the nameed
queue parameter in options or [] if it has not been properly specified.

isFatal x returns true iff x denotes a fatal, unrecoverable error.


isFatal :: cat -> Bool
isFatal _ = True

lookupQueue :: String -> Options -> [String]


lookupQueue name options =
case M.lookup name options of
Just (ParamQueue q) -> F.toList q
_
-> []

3.8

Filename globbing

4.4

Data types

A phase of a compiler will typically return a list of WEMessages,


along with the result of the phase if there is one. A WEResult distinguishes the cases that have passed (for example with only warnings) from those that have failed (with a fatal error). a is the phases
result.
data WEResult cat a =

glob paths returns the really existing file names that match the
paths which may contain wildcards.
# if __GLASGOW_HASKELL__ < 705
-- legacy for hobbit
# else
glob :: [FilePath] -> IO [FilePath]
glob paths = do
(pathss, _) <- G.globDir (map G.compile paths) []
return $ L.nub $ concat pathss
# endif

Util.Errors

Module ABR.Util.Errors provides a framework for collecting


warnings and errors, displaying them, and exiting on fatal errors.
module ABR.Util.Errors (
WEMessage(..), WEResult(..),
returnPass, returnFail, returnWarn
) where
import Control.Applicative
import ABR.Util.Pos

April 3, 2015

4.5

Returning
warning

WEPass [WEMessage cat] a


| WEFail [WEMessage cat]

successfully,

failing

or

returnPass x returns x as a passing value.


returnPass :: a -> WEResult cat a
returnPass = return
returnFail wem returns a failing message, wem.
returnFail :: WEMessage cat -> WEResult cat ()
returnFail wem = WEFail [wem]
returnWarn wem returns a warning message, wem.
returnWarn :: WEMessage cat -> a -> WEResult cat a
returnWarn wem = WEPass [wem]

4.6
4.6.1

Instances
Functor

instance Functor (WEResult cat) where


fmap f r = case r of
WEPass wems x -> WEPass wems (f x)
WEFail wems
-> WEFail wems

4.6.2

Applicative

instance Applicative (WEResult cat) where


pure = WEPass []
r <*> s = case r of
WEPass wems f -> case s of
WEPass wems x -> WEPass (wems ++ wems) (f x)
WEFail wems
-> WEFail (wems ++ wems)
WEFail wems
-> WEFail wems

4.6.3

Monad

Making a WEResult a monad makes possible the silent accumulation messages as the computation proceeds, and stopping when its
failed.
instance Monad (WEResult cat) where
return = WEPass []
r >>= f = case r of
WEPass wems x -> case f x of
WEPass wems x -> WEPass (wems ++ wems) x
WEFail wems
-> WEFail (wems ++ wems)
WEFail wems
-> WEFail wems

Util.Open

Module ABR.Util.Open allows access to system facilities to open


things like URLs.
{-# LANGUAGE CPP #-}
module ABR.Util.Open (
openURL
) where
import System.Exit
import System.Process
import ABR.Data.List

5.1

5.4

Opening a URL

openURL url opens the url in the default browser.


openURL :: String -> IO ExitCode
openURL url = system $
openTool ++ " " ++ delimiter ++
replaceAll "&" ampersand url ++ delimiter

Util.Pos

Module ABR.Util.Pos defines a type for a position in a source


code.
module ABR.Util.Pos (
Line, Col, Pos, HasPos(..), precedes
) where

6.1

Maintenance notes

Reviewed
Reviewed
Reviewed
Reviewed
Reviewed

6.2

2015-02-02.
2014-05-29:
2013-11-06.
2012-11-23:
2009-04-13:

Passed hlint.
Made all the -Walls go away.
Moved to ABR.Util from ABR.Parser.
Split from ABR.Parser.

Positions in a source

To report error the position, Pos , of a character or token in a source


is required. The first line and column are indicated with Line and
Col values of 0. A negative Line value indicates Dont know
where.
type Line = Int
type Col = Int
type Pos = (Line, Col)

Maintenance notes

New 2015-04-02.

6.3

5.2

Positions get embedded in all kinds of parse tree types. Having one
overloaded function that projects out a Pos is useful. Make parse
tree types with postions in them an instance of HasPos .

System specifics

Overloaded projector

openTool is the system specific comand line tool for opening a URL
in the default browser. delimiter is the character(s) that should
delimit the URL. ampersand is the character(s) should replace any
ampersands in the URL.
openTool, delimiter, ampersand :: String
openTool =
#ifdef darwin_HOST_OS
"open"
#endif
#ifdef linux_BUILD_OS
"xdg-open"
#endif
#ifdef mingw32_BUILD_OS
"start"
#endif
delimiter =
#ifdef mingw32_BUILD_OS
""
#else
"\""
#endif
ampersand =
#ifdef mingw32_BUILD_OS
"^&"
#else
"&"
#endif

5.3

Tests

class HasPos a where


getPos x returns the position of x.
getPos :: a -> Pos
getPos = error "undefined HasPos instance"

6.3.1

Container intances

instance (HasPos a, HasPos b) => HasPos (Either a b) where


getPos e = case e of
Left x -> getPos x
Right x -> getPos x

6.4

Relative positions

p1 precedes p2 if p1 comes earlier in than p2 .


precedes :: Pos -> Pos -> Bool
(l1,c1) precedes (l2,c2) =
l1 < l2 || l1 == l2 && c1 < c2

Util.Time

Module ABR.Util.Time collects time-related functions. The new


time libraries are complicated. So are the old ones. The transition is
not smooth. GHC 7.0.x (hobbit) is not compatible with the current
versions, hence all the legacy stuff implemented with conditional
compilation.

testOpenURL :: IO ExitCode
testOpenURL = openURL "http://www.ict.griffith.edu.au/arock/itp/"
{-# LANGUAGE CPP #-}

April 3, 2015

module ABR.Util.Time (
LegacyTime,
C.UTCTime, LT.ZonedTime, LT.LocalTime,
utcToZonedTime, utcToLocalTime,
getCurrentUTCTime, getCurrentLocalTime,
getCurrentZonedTime,
TimeFormat, dateThenTime1, dateThenTime2,
formatTime, formatUTCTime,
currentTime, fileModTime,
LegacyTimes (getCurrentLegacyTime,
diffSec, diffMin, diffHour, diffDay,
formatLegacyTime)
) where
import qualified Data.Time.Clock as C
import qualified Data.Time.Format as F
import qualified Data.Time.LocalTime as LT
import qualified System.Directory as D
import qualified System.Locale as L
# if __GLASGOW_HASKELL__ < 705
-- legacy for hobbit
import qualified System.Time as ST
# else
# endif

7.1

Maintenance notes

Reviewed 2015-02-02. Passed hlint.


Reviewed 2014-05-29: Made all the -Walls go away.
New 2013-11-27.

7.2

Data types

LegacyTime is a type synonym for either ClockTime or UTCTime,


whichever is returned by getModificationTime.
type LegacyTime =
# if __GLASGOW_HASKELL__ < 705
-- legacy for hobbit
ST.ClockTime
# else
C.UTCTime
# endif

7.3

Time conversions

utcToZonedTime converts a UTC time to a ZonedTime.


utcToZonedTime :: C.UTCTime -> IO LT.ZonedTime
utcToZonedTime = LT.utcToLocalZonedTime
utcToLocalTime converts a UTC time to a LocalTime.
utcToLocalTime :: C.UTCTime -> IO LT.LocalTime
utcToLocalTime utc = do
tz <- LT.getCurrentTimeZone
return $ LT.utcToLocalTime tz utc

7.5

Format strings

A TimeFormat is a time format string.


type TimeFormat = String
These are strings to use with the time formatTime.
format
dateThenTime1
dateThenTime2

produces
Wed 27 Nov 2013 10:16:14 EST
Wed 27 Nov 2013 10:16:14

dateThenTime1, dateThenTime2 :: TimeFormat


dateThenTime1 = "%a %d %b %Y %H:%M:%S %Z"
dateThenTime2 = "%a %d %b %Y %H:%M:%S"

7.6

Formatting times

formatTime formats a time using the default locale.


formatTime :: F.FormatTime t => TimeFormat -> t -> String
formatTime = F.formatTime L.defaultTimeLocale
formatUTCTime returns a formatted UTC time in local time
showing the time zone.
formatUTCTime :: C.UTCTime -> IO String
formatUTCTime utc = do
zt <- utcToZonedTime utc
return $ formatTime dateThenTime1 zt

7.7

Formated current time

currentTime returns a the formatted current time in local time


showing the time zone.
currentTime :: IO String
currentTime = do
zt <- getCurrentZonedTime
return $ formatTime dateThenTime1 zt

7.8

Formated file modification time

fileModTime returns the formatted modification time of a file.


fileModTime :: FilePath -> IO String
fileModTime path = do
legacyT <- D.getModificationTime path
formatLegacyTime legacyT

7.9

Overloading for old/new time systems

Class LegacyTimes overloads time operations with old and new


time types.
class LegacyTimes t where
getCurrentLegacyTime gets the current time.
getCurrentLegacyTime :: IO t

7.4

Getting the current time

getCurrentUTCTime gets the current time in UTC.


getCurrentUTCTime :: IO C.UTCTime
getCurrentUTCTime = C.getCurrentTime
getCurrentZonedTime gets the current local time and time zone.
getCurrentZonedTime :: IO LT.ZonedTime
getCurrentZonedTime = do
utc <- C.getCurrentTime
utcToZonedTime utc
getCurrentLocalTime gets the current local time. Local times can
not display the time zone on formatting.
getCurrentLocalTime :: IO LT.LocalTime
getCurrentLocalTime = do
utc <- C.getCurrentTime
utcToLocalTime utc

April 3, 2015

t1 ` diffSec ` t2 returns t1 t2 rounded down (floor) to whole seconds.


diffSec :: t -> t -> Int
t1 ` diffMin ` t2 returns t1 t2 rounded down to whole minutes.
diffMin :: t -> t -> Int
t1 diffMin t2 = (t1 diffSec t2) div 60
t1 ` diffHour ` t2 returns t1 t2 rounded down to whole hours.
diffHour :: t -> t -> Int
t1 diffHour t2 = (t1 diffMin t2) div 60
t1 ` diffDay ` t2 returns t1 t2 rounded down to whole days.
diffDay :: t -> t -> Int
t1 diffDay t2 = (t1 diffHour t2) div 24
formatLegacyTime returns a formatted legacy time in local time
showing the time zone.
formatLegacyTime :: t -> IO String

7.10

Instances

7.10.1

8.4

LegacyTimes

instance LegacyTimes C.UTCTime where


getCurrentLegacyTime = getCurrentUTCTime

Parallel checks

r1 +? r2 combines check results r1 and r2 . If both results are passes


only r2 is returned. If only one result is a pass, then the other failing
result is returned. If both are fails, then a fail is returned with the
catenation of the error messages. This leads to the restriction that
the fail data type must be a list type.

t1 diffSec t2 = floor (t1 C.diffUTCTime t2)


formatLegacyTime = formatUTCTime
# if __GLASGOW_HASKELL__ < 705
instance LegacyTimes ST.ClockTime where
getCurrentLegacyTime = ST.getClockTime
t1 diffSec t2 =
let diff = t1 ST.diffClockTimes t2
in ((((ST.tdYear diff * 12 +
ST.tdMonth diff) * 31 +
ST.tdDay diff) * 24 +
ST.tdHour diff) * 60 +
ST.tdMin diff) * 60 +
ST.tdSec diff
formatLegacyTime clockT = do
calT <- ST.toCalendarTime clockT
return $ ST.formatCalendarTime
L.defaultTimeLocale dateThenTime1 calT
# else
# endif

Control.Check

Module ABR.Control.Check implements checks as operations to be


performed that may succeed or fail. Checks are often performed in
a sequence. Composing lots of checks can lead to big, ugly cascades
of case expressions. This module provides a way to do it more
compactly and neatly.2
module ABR.Control.Check (
CheckResult(..), Check, (&?), (+?), (??), (*?), (#?)
) where

(+?) :: CheckResult a [b] -> CheckResult a [b]


-> CheckResult a [b]
r1 +? r2 = case r1 of
CheckPass _ -> r2
CheckFail m1 -> case r2 of
CheckPass _ -> r1
CheckFail m2 -> CheckFail (m1 ++ m2)
c #? xs applies check c to each of the elements of xs in parallel,
returning only the last result if all checks pass or all of the error
messages catenated if any checks fail.
(#?) :: Check a b [c] -> Check [a] b [c]
c #? xs = foldl1 (+?) $ map c xs
cs ?? x applies all the checks in cs to x in parallel; returning only
the last result if all checks pass or all of the error messages catenated
if any checks fail.
(??) :: [Check a b [c]] -> Check a b [c]
cs ?? x = foldl1 (+?) $ pam cs x
cs *? xs applies all the checks in cs to all of the elements of xs in
parallel; returning only the last result (the last check applied to the
last thing) if all checks pass or all of the error messages catenated
if any checks fail.
(*?) :: [Check a b [c]] -> Check [a] b [c]
cs *? xs = foldl1 (+?) [c x | c <- cs, x <- xs]

Control.List

import ABR.Control.List

Module ABR.Control.List implements control abstractions involving lists.

infixl 2 &?, +?, #?, ??, *?

module ABR.Control.List (pam) where

8.1

9.1

Maintenance notes

Reviewed
Reviewed
Reviewed
Reviewed

8.2

2015-02-02. Passed hlint.


2014-05-28: Made all -Walls go away.
20013-11-07.
2009-04-08: Changed to ABR.Control.Check.

Data type

Maintenance notes

Reviewed 2015-02-02. Passed hlint.


Reviewed 2014-05-28: Made all -Walls go away.
Reviewed 2013-11-08.
New 2009-04-08: Separated from ABR.List.

9.2

Backwards map

The result of a Check, a CheckResult is either a CheckPass with


the correct result, or a CheckFail with some alternate data, probably an error message string.

pam fs x returns the list of results obtained by applying all the


functions in fs to x.

data CheckResult passType failType =

pam :: [a -> b] -> a -> [b]


pam fs x = [f x | f <- fs]

CheckPass passType
| CheckFail failType

deriving (Eq, Ord, Show)


A Check takes some object and returns a CheckResult.
type Check objectType passType failType
= objectType -> CheckResult passType failType

8.3

Sequencing checks

c1 &? c2 sequence composes check c1 and c2 in that order. c1 is


applied first. If it succeeds, then c2 is applied to the result.
(&?) :: Check a b d
(&?) c1 c2 x = case
CheckPass x ->
CheckFail x ->
2 Thanks

-> Check b c d -> Check a c d


c1 x of
c2 x
CheckFail x

to Daniel Young for suggested extensions to this mod-

ule.

April 3, 2015

10

Control.Map

Module ABR.Control.Map implements control abstractions involving Data.Map.Maps.


module ABR.Control.Map (lookupGuard) where
import qualified Data.Map as M

10.1

Maintenance notes

Reviewed 2015-02-02. Passed hlint.


Reviewed 2014-05-28: Made all -Walls go away.
Reviewed 2013-11-10.
New 2012-11-01: replacement for ABR.Data.BSTree.lookupGuard.

10.2

lookupGuard

lookupGuard m keys handler process tries to look up the keys.


If any are missing the handler is applied to the first missing key
otherwise the process is applied to the list of values successfully
looked up.
lookupGuard :: Ord a => M.Map a b -> [a] -> (a -> c)
-> ([b] -> c) -> c
lookupGuard m keys handler process
= lookupGuard keys []
where
lookupGuard [] vals
= process vals
lookupGuard (k:ks) vals
= case M.lookup k m of
Nothing
->
handler k
Just stuff ->
lookupGuard ks (vals ++ [stuff])

11

Data.List

merge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
merge _ [] ys = ys
merge _ xs [] = xs
merge lt (x:xs) (y:ys)
| x lt y = x : merge lt xs (y:ys)
| otherwise = y : merge lt (x:xs) ys
split xs splits xs into two lists of the alternate elements of xs.
split :: [a] -> ([a],[a])
split xs = case xs of
[]
-> ([],[])
[x]
-> ([x],[])
(x1:x2:xs) -> let (xs1,xs2) = split xs
in (x1:xs1, x2:xs2)
cartProd produces the cartesian product of an arbitrary number
of lists. That is, cartProd [xs 1 , xs 2 , . . .] returns xs 1 xs 2 . . .
Note: Prelude.sequence can be used to do the same job.
cartProd :: [[a]] -> [[a]]
cartProd xs = case xs of
[]
-> [[]]
xs:xss -> [x : ys | x <- xs, ys <- cartProd xss]

Module ABR.Data.List is a collection of functions that operate on


lists.

interleave x xs returns the list of lists formed by inserting x in


each possible place in xs.

module ABR.Data.List (
merge, msort, split, cartProd, interleave, separate,
fragments, fragments, dropEach, permutations,
permutations, combinations, subBag, bagElem,
powSet, powSet_ge1, powSet, powSet_ge1,
properSublists, pPlus, meet, disjoint, allUnique,
duplicates, snub, (+:), mnub, isSubset, findSubset,
noSuperSets, isSubSequence, notSubSequence, chop,
chops, subsSuffix, odiff, osect, ounion,
sortByLength, trimN, trim2, replace1, replaceAll
) where

interleave :: a -> [a] -> [[a]]


interleave x ys = case ys of
[]
-> [[x]]
y:ys -> (x:y:ys) : map (y:) (interleave x ys)

import qualified Data.List as L

11.1

Maintenance notes

Reviewed 2015-02-02. Passed hlint.


Reviewed 2014-05-29: Made all the -Walls go away.
Reviewed 2013-11-10.

11.2

Language tweaks

undefined :: String -> a


undefined label = error $
"Intentional undefined at module ABR.Data.List, \
\ with label \"" ++ label ++ "\"."

11.3

Sorting

msort lt xs sorts xs using lt as the less-than operator.


msort :: (a ->
{-# DEPRECATED
msort _ [] =
msort _ [x] =
msort lt xs =

a -> Bool) -> [a] -> [a]


msort "Use Data.List.sort" #-}
[]
[x]
let (ys,zs) = split xs
in merge lt (msort lt ys) (msort lt zs)

sortByLength xss sorts a list of lists into non-descending order of


length.
sortByLength :: [[a]] -> [[a]]
sortByLength = map snd .
L.sortBy (\(a,_) (b,_) -> compare a b) .
map (\xs -> (length xs, xs))

11.4

Combinatorics

merge lt xs ys merges lists xs and ys preserving the non-descending


order in xs and ys using lt to decide what is less that what.

April 3, 2015

separate xs yss concats yss, with xs interspersed.


separate :: [a] -> [[a]] -> [a]
{-# DEPRECATED separate "Use Data.List.intercalate" #-}
separate = L.intercalate
fragments xs returns the list of fragments
(beforeElems, elem, afterElems) for each element of xs. The elements in beforeElems are in reverse order with respect to xs.
fragments :: [a] -> [([a],a,[a])]
fragments = f []
where
f _ []
= []
f b (e:a) = (b,e,a) : f (e:b) a
fragments xs returns the list of fragments (elem, otherElems) for
each elements of xs. The elements in otherElems are in no particular
order.
fragments :: [a] -> [(a,[a])]
fragments = map (\(xs,y,zs) -> (y, xs ++ zs)) . fragments
dropEach xs returns the list of lists obtained by deleting each element of xs.
dropEach :: [a] -> [[a]]
dropEach = map (\(xs,_,zs) -> xs ++ zs) . fragments
permutations k xs returns all the permutations of k elements selected from xs. Precondition: 0 <= k <= length xs.
permutations
permutations
permutations
permutations

:: Int
_ [] =
0 _ =
k xs =

-> [a] -> [[a]]


[[]]
[[]]
[x : xs | (bs,x,as) <- fragments xs,
xs <- permutations (k-1) (bs++as)]

permutations xs returns all the permutations of the elements of


xs.
permutations :: [a] -> [[a]]
{-# DEPRECATED permutations "Use Data.List.permutations" #-}
permutations [] = [[]]
permutations (x:xs) = [zs | ys <- permutations xs,
zs <- interleave x ys]
combinations k xs returns all the combinations of k elements
drawn from xs. Precondition: 0 <= k <= length xs.

10

combinations :: Int -> [a] -> [[a]]


combinations k xs
= comb k (length xs) xs
where
comb :: Int -> Int -> [a] -> [[a]]
comb k l (x:xs)
| k == 0
= [[]]
| k == l
= [x:xs]
| otherwise = map (x:) (comb (k-1) (l-1) xs)
++ comb k (l-1) xs
comb _ _ [] = undefined "comb"
powSet xs returns the list of sub-lists of xs. This version does not
return them in an order that monotonically increases in length.
powSet :: [a] -> [[a]]
powSet []
= [[]]
powSet (x:xs) = let pss = powSet xs
in pss ++ map (x:) pss
powSet ge1 xs returns the list of sub-lists of xs with at least 1
element. This version does not return them in an order that monotonically increases in length.
powSet_ge1 :: [a] -> [[a]]
powSet_ge1 = tail . powSet

11.6

Set-like operations

allUnique xs returns True iff all elements of xs are unique.


allUnique :: Eq a => [a] -> Bool
allUnique [] = True
allUnique (x:xs)
| x elem xs = False
| otherwise
= allUnique xs
duplicates xs returns all of the elements of xs that are duplicated.
duplicates :: Eq a => [a] -> [a]
duplicates [] = []
duplicates (x:xs)
| x elem xs = x : duplicates xs
| otherwise
= duplicates xs
snub xs returns the unique elements of xs in non-descending order,
and does it in O(N log N ) time.
snub :: Ord a => [a] -> [a]
snub xs
= case xs of
(_:_:_) -> mnub (snub ys) (snub zs)
_
-> xs
where
(ys, zs) = split xs
X +: x returns X {x}.

powSet xs returns the list of sub-lists of xs. This version does


return them in an order that monotonically increases in length.
powSet :: [a] -> [[a]]
powSet xs = [] : powSet_ge1 xs
powSet ge1 xs returns the list of sub-lists of xs with at least 1 element. This version does return them in an order that monotonically
increases in length.
powSet_ge1 :: [a] -> [[a]]
powSet_ge1 xs
= concat [combinations k xs | k <- [1..length xs]]
properSublists xs returns the list of strict sub-lists of xs with at
least 1 element, in an order that monotonically increases in length.
properSublists :: [a] -> [[a]]
properSublists xs = case xs of
[] -> []
_ -> [] : concat [combinations k xs
| k <- [1..length xs - 1]]

11.5

Bag-like operations

xs subBag ys returns True iff every element of xs occurs at least


as frequently in ys as it does in xs.
subBag :: Eq a => [a] -> [a] -> Bool
subBag [] _
= True
subBag (x:xs) ys = case del x ys of
(False, _) -> False
(True, ys) -> subBag xs ys
where
del _ [] = (False,[])
del x (y:ys)
| x == y
= (True, ys)
| otherwise = case del x ys of
(False, _) -> (False, [])
(_, ys)
-> (True, y : ys)
bagElem xs xss returns True iff xs or some permutation of xs is an
element of xss.

infixl 5 +:
(+:) :: Ord a => [a] -> a -> [a]
xs +: x = snub (x : xs)
mnub xs ys merges lists xs and ys which must be in strictly ascending order. Any elements that occur in xs and ys occur only once in
the result.
mnub :: Ord a => [a] -> [a] -> [a]
mnub xs ys = case xs of
[]
-> ys
x : xs -> case ys of
[]
-> xs
y : ys | x < y
-> x : mnub xs ys
| x == y
-> x : mnub xs ys
| otherwise -> y : mnub xs ys
isSubset xs ys returns True iff xs ys. Precondition: xs and ys
are in strictly ascending order.
isSubset
isSubset
isSubset
isSubset

:: (Ord a) => [a] -> [a] ->


[]
_
=
_
[]
=
(x:xs) (y:ys) | x == y
=
| x < y
=
| otherwise =

Bool
True
False
isSubset xs ys
False
isSubset (x:xs) ys

isProperSubset xs ys returns True iff xs ys. Precondition: xs


and ys are in strictly ascending order.
isProperSubset :: (Ord a) => [a] -> [a] -> Bool
isProperSubset xs ys = xs /= ys && xs isSubset ys
odiff xs ys returns the set difference xs ys. Precondition: xs
and ys are in strictly ascending order.
odiff :: (Eq a, Ord a) => [a] -> [a] -> [a]
odiff xs ys = case xs of
[]
-> []
x:xs -> case ys of
[]
-> x : xs
y:ys | x < y
-> x : odiff xs (y:ys)
| x == y
-> odiff xs ys
| otherwise -> odiff (x:xs) ys
osect xs ys returns the set intersection xs ys. Precondition: xs
and ys are in strictly ascending order.

osect :: (Eq a, Ord a) => [a] -> [a] -> [a]


osect xs ys = case xs of
bagElem :: Eq a => [a] -> [[a]] -> Bool
[]
-> []
bagElem _ []
x:xs -> case ys of
= False
[]
-> []
bagElem xs (xs:xss)
y:ys | x < y
-> osect xs (y:ys)
| null (xs L.\\ xs) && null (xs L.\\ xs) = True
| x == y
-> x : osect xs ys
| otherwise
= bagElem xs xss
| otherwise -> osect (x:xs) ys

April 3, 2015

11

ounion xs ys returns the set union xs ys. Precondition: xs and


ys are in strictly ascending order.
ounion :: (Eq a, Ord a) =>
ounion xs ys = case xs of
[]
-> ys
x:xs -> case ys of
[]
->
y:ys | x < y
->
| x == y
->
| otherwise ->

[a] -> [a] -> [a]

chop :: Eq a => a -> [a] -> [[a]]


chop x xs = case xs of
[] -> []
xs -> let (xs,xs) = break (== x) xs
in xs : case xs of
[]
-> []
_ : xs -> chop x xs

x
x
x
y

chops bs xs returns the sublists in xs that are separated by sequences equal to bs.

:
:
:
:

xs
ounion xs (y:ys)
ounion xs ys
ounion (x:xs) ys

findSubset xs ys returns Just xs if xs ys or Just ys if ys xs,


otherwise Nothing. Precondition: xs and ys are in strictly ascending
order.
findSubset :: Ord a => [a] -> [a] -> Maybe [a]
findSubset lefts rights = fss lefts rights
where
fss []
[]
= Just lefts
fss []
_
= Just lefts
fss _
[]
= Just rights
fss (x:xs) (y:ys) | x == y
= fss xs ys
| x < y
= fssR xs (y:ys)
| otherwise = fssL (x:xs) ys
fssR _
[]
= Just rights
fssR []
_
= Nothing
fssR (x:xs) (y:ys) | x == y
= fssR xs ys
| x < y
= fssR xs (y:ys)
| otherwise = Nothing
fssL []
_
= Just lefts
fssL _
[]
= Nothing
fssL (x:xs) (y:ys) | x == y
= fssL xs ys
| y < x
= fssL (x:xs) ys
| otherwise = Nothing
noSuperSets xss reduces xss by removing all elements of xss that
are supersets of any other elements of xss. Preconditions: All elements of xss are in strictly ascending order; and all elements of xss
are unique.
noSuperSets :: Ord a => [[a]] -> [[a]]
noSuperSets xss = [xs | xs <- xss,
not (any (isProperSubset xs) xss)]
disjoint A B returns True iff A B = {}.
disjoint :: Eq a => [a] -> [a] -> Bool
disjoint as bs = and [a /= b | a <- as, b <- bs]
meet A B returns True iff A B 6= {}.
meet :: Eq a => [a] -> [a] -> Bool
meet as bs = or [a == b | a <- as, b <- bs]
pPlus L H returns P + (L, H) = {K : K L and K 6= {} and
H K = {}}.
pPlus :: Eq a => [a] -> [a] -> [[a]]
pPlus l h = filter (disjoint h) (powSet_ge1 l)

11.7

Subsequence operations

isSubSequence ps cs returns True iff ps is a sequence that occurs


in cs. This implementation uses only a brute force algorithm, O(m
n), for m = length ps and n = length cs.
isSubSequence :: Eq a => [a] -> [a] -> Bool
isSubSequence ps cs = case cs of
[]
-> False
_:cs -> L.isPrefixOf ps cs || isSubSequence ps cs
notSubSequence ps cs returns True iff ps is not a sequence that
occurs in cs.
notSubSequence :: Eq a => [a] -> [a] -> Bool
notSubSequence p w = not $ isSubSequence p w
chop x xs returns the sublists in xs that are separated by elements
equal to x.

April 3, 2015

chops :: Eq a => [a] -> [a] -> [[a]]


chops bs xs
| null bs
= error "chops null"
| otherwise = chp xs
where
n = length bs
chp xs
| null xs
= []
| bs L.isPrefixOf xs = chp (drop n xs)
| otherwise
=
let (ys,zs) = brk xs
in ys : chp zs
brk xs
| bs L.isPrefixOf xs = ([], drop n xs)
| otherwise
= case xs of
[] -> ([],[])
x : xs ->
let (ys,zs) = brk xs
in (x : ys, zs)
subsSuffix sep suf xs replaces anything after the rightmost occurrence of sep in xs with suf . If suf does not occur in xs, then
sep and suf are appended. Use this to create otput file names from
input file names.
subsSuffix :: Eq a => a -> [a] -> [a] -> [a]
subsSuffix sep suf xs =
let rs = reverse xs
(ys, zs) = break (== sep) rs
rs = if null zs then
reverse suf ++ sep : ys
else
reverse suf ++ zs
in reverse rs
trimN n xs drops n elements from both ends of a list.
trimN :: Int -> [a] -> [a]
trimN n = reverse . drop n . reverse . drop n
trim2 xs drops 2 elements from both ends of a list. Useful for
comments and the like.
trim2 :: [a] -> [a]
trim2 = trimN 2
replace1 as bs xs replaces the first occurence of as in xs with bs.
replace1 :: Eq a => [a] -> [a] -> [a] -> [a]
replace1 as bs xs = case xs of
[] -> []
x : xs | as L.isPrefixOf xs ->
bs ++ drop (length as) xs
| otherwise ->
x : replace1 as bs xs
replaceAll as bs xs replaces all occurences of as in xs with bs.
replaceAll :: Eq a => [a] -> [a] -> [a] -> [a]
replaceAll as bs xs = case xs of
[] -> []
x : xs | as L.isPrefixOf xs ->
bs ++ replaceAll as bs (drop (length as) xs)
| otherwise ->
x : replaceAll as bs xs

12

Data.NameTable

Module ABR.Data.NameTable implements structures for the efficient accumulation of names, assigning unique, sequential integers
to each name and mapping between them.

12

module ABR.Data.NameTable (
NameTable, newNameTable, insertName, lookupName,
NameArray, makeNameArray
) where

13

Data.Supply

Module ABR.Data.Supply implements a name supply using a mutable variable in the IO monad.

import Data.Array.IArray
import Data.Array.IO
import qualified Data.Map as M

module ABR.Data.Supply (
Supply, newSupply, supplyNext, peekNext
) where

import ABR.Data.Supply

import Data.IORef

12.1

Maintenance notes

Reviewed 2015-02-02.
Reviewed 2014-06-03:
Reviewed 2013-11-21:
for IO functions.
Reviewed 2012-11-24:

Passed hlint.
Made all the -Walls go away.
Switch from hashtables to maps. Less need
Moved into ABR.Data.

13.1

Maintenance notes

Reviewed 2015-02-02. Passed hlint.


Reviewed 2014-06-03: Made all the -Walls go away.
Reviewed 2013-11-22. Reviewed 2012-11-24: Moved into ABR.Data.

13.2

Data types

A Supply is a value of any enumerated type.

12.2

Data types

type Supply a =
IORef a

A NameTable is a pair of:


a map from strings to integers; and
a supply of sequential integers starting from 0.
type NameTable =
(M.Map String Int, Supply Int)
For the reverse mapping an array of names, a NameArray , is optimal.
type NameArray = Array Int String

12.3

Creating a name table

newNameTable size creates a NameTable.


newNameTable :: IO NameTable
newNameTable = do
s <- newSupply 0
return (M.empty, s)

13.3

Creating a supply

newSupply first creates a new Supply that will commence with


first.
newSupply :: a -> IO (Supply a)
newSupply = newIORef

13.4

Extracting values from a supply

supplyNext supply returns the next value from the supply.


supplyNext :: Enum a => Supply a -> IO a
supplyNext r = do
i <- readIORef r
let i = succ i
writeIORef r i
return i
peekNext supply returns the next value from the supply, but does
not change the supply, so that the next value extracted will be the
same.

insertName t n inserts name n into the name table t. If the name


already exists, nothing happens. If the name is new, it is added to
the table and assigned the next sequence number.

peekNext :: Supply a -> IO a


peekNext = readIORef

insertName :: NameTable -> String -> IO NameTable


insertName (m,s) n = case M.lookup n m of
Just _ -> return (m, s)
Nothing -> do
i <- supplyNext s
return (M.insert n i m, s)

14

Debug.Array

Module ABR.Debug.Array is used to help debug programs that use


arrays.
module ABR.Debug.Array (array, accumArray, (!!!)) where
import Data.Array

12.4

Looking up by name

lookupName t n retrieves the sequence number for the given name


n in name table t, provided it exists.
lookupName :: NameTable -> String -> Maybe Int
lookupName (m,_) n = M.lookup n m

12.5

Creating a name array

makeNameArray t builds an array for mapping sequence numbers


back to names.
makeNameArray :: NameTable -> IO NameArray
makeNameArray (m,s) = do
size <- peekNext s
a <- newArray (0, size - 1) "" :: IO (IOArray Int String)
let add :: (String, Int) -> IO ()
add (n,i) = writeArray a i n
mapM_ add $ M.toList m
freeze a

April 3, 2015

14.1

Maintenance notes

Reviewed
Reviewed
Reviewed
Reviewed

2015-02-02. Passed hlint.


2014-06-03: Made all the -Walls go away.
2013-11-22.
2009-10-27: Changed to ABR.Debug.Array .

14.2

Functions

A !!! is a replacement for ! that displays a different error message


so you can pin down which array indexing operation is out of range.
(!!!) :: (Ix i, Show i, Show e) => Array i e -> i -> e
a !!! i =
let b = bounds a
in if inRange b i
then a ! i
else error $
"ABR.Array.!!! Array index out of bounds:\n"
++ "bounds = " ++ show b ++ "\n"
++ "index = " ++ show i ++ "\n"
++ "array = " ++ show a ++ "\n"

13

A array is a replacement for array that displays a different error


message so you can pin down which array indexing operation is out
of range.
array :: (Ix i, Show i, Show e) =>
(i,i) -> [(i,e)] -> Array i e
array b ies =
case [i | (i,_) <- ies, not (inRange b i)] of
[] -> array b ies
is -> error $
"ABR.array.!!! Array indices out of bounds:\n"
++ "bounds = " ++ show b ++ "\n"
++ "indices = " ++ show is ++ "\n"
++ "pairs = " ++ show ies ++ "\n"
A accumArray is a replacement for accumArray that displays a
different error message so you can pin down which array indexing
operation is out of range.
accumArray :: (Ix i, Show i, Show a) => (e -> a -> e)
-> e -> (i,i) -> [(i,a)] -> Array i e
accumArray f e b ies =
case [i | (i,_) <- ies, not (inRange b i)] of
[] -> accumArray f e b ies
is -> error $
"ABR.accumArray.!!! Array indices out of bounds:\n"
++ "bounds = " ++ show b ++ "\n"
++ "indices = " ++ show is ++ "\n"
++ "pairs = " ++ show ies ++ "\n"

accumArray :: (Ix i, Show i, Show a) => (e -> a -> e)


-> e -> (i,i) -> [(i,a)] -> Array i e
accumArray f e b ies =
case [i | (i,_) <- ies, not (inRange b i)] of
[] -> accumArray f e b ies
is -> error $
"ABR.accumArray.!!! Array indices out of bounds\
\:\n" ++ "bounds = " ++ show b ++ "\n"
++ "indices = " ++ show is ++ "\n"
++ "pairs = " ++ show ies ++ "\n"

16

Parser

The ABR.Parser module provides a framework for lexical analysis


and parsing using parser combinators [1, 2].
module ABR.Parser (
Msg, Could(Fail, Error, OK), Analyser, succeedA,
epsilonA, failA, errorA, ( <|> ), ( <&> ), ( @> ), (
#> ), cons, some, many, optional, someUntil,
manyUntil, ( &> ), ( <& ), alsoSat, alsoNotSat,
dataSatisfies, dataSatisfies, total, nofail,
nofail, preLex, Lexeme, Tag, Lexer, TLP, TLPs,
satisfyL, literalL, ( %> ), ( <&&> ), ( <++> ), ( &%>
), soft, tagFilter, tokenL, endL, listL, Parser,
tagP, lineNo, literalP, errMsg, warnMsg
) where
import ABR.Util.Pos

15

Debug.IArray

Module ABR.Debug.IArray is used to help debug programs that


use immutable arrays.
module ABR.Debug.IArray (array, accumArray, (!!!)) where
import Data.Array.IArray

15.1

Maintenance notes

Reviewed 2015-02-02. Passed hlint.


Reviewed 2014-06-03: Made all the -Walls go away.
sReviewed 2013-11-22.
Reviewed 2009-10-27: Changed to ABR.Debug.IArray .

15.2

Functions

A !!! is a replacement for ! that displays a different error message


so you can pin down which array indexing operation is out of range.
(!!!) :: (IArray a e, Ix i, Show i, Show (a i e)) =>
a i e -> i -> e
a !!! i =
let b = bounds a
in if inRange b i
then a ! i
else error $
"ABR.DebugArray.!!! Array index out of bounds:\n"
++ "bounds = " ++ show b ++ "\n"
++ "index = " ++ show i ++ "\n"
++ "array = " ++ show a ++ "\n"
A array is a replacement for array that displays a different error
message so you can pin down which array indexing operation is out
of range.
array :: (Ix i, Show i, Show e) =>
(i,i) -> [(i,e)] -> Array i e
array b ies =
case [i | (i,_) <- ies, not (inRange b i)] of
[] -> array b ies
is -> error $
"ABR.array.!!! Array indices out of bounds:\n"
++ "bounds = " ++ show b ++ "\n"
++ "indices = " ++ show is ++ "\n"
++ "pairs = " ++ show ies ++ "\n"
A accumArray is a replacement for accumArray that displays a
different error message so you can pin down which array indexing
operation is out of range.

April 3, 2015

infixr 6 <&>, &>, <&, <&&>, <++>


infixr 5 @>, #>, %>, &%>
infixr 4 <|>

16.1

Maintenance notes

Reviewed
Reviewed
Reviewed
Reviewed

2015-02-03. Passed hlint.


2014-05-30: Made all the -Walls go away.
2013-11-22.
2009-04-13: Split up.

16.2

Error messages

An error message, Msg , generated by an analyser is a String.


type Msg = String

16.3

Results

An analyser could succeed, fail or generate an error. The Could


type wraps around any other type to indicate success (with OK ),
failure (with Fail ), or an immediately identifiable error (with
Error ). Failure or error values return a diagnostic message, and a
position in the source. Failure means: Its not that, try something
else. An error is unrecoverable.
data Could a = Fail Pos Msg | Error Pos Msg | OK a
deriving (Eq, Ord, Show)

16.4

Analysers

An Analyser is a higher-level abstraction of both lexers and


parsers. An analyser is a function that tries to accept a list of inputs of type a with their positions, and return a value constructed
from consumed inputs of type b (a parse tree for example), and any
unconsumed inputs. Alternately it could fail or generate an error.
By convention, functions that are analysers have names that end
with a capital A.
type Analyser a b = [(a,Pos)] -> Could (b, [(a,Pos)])

16.5

Elementary analysers

This is the simplest analyser. succeedA v succeeds with a predetermined value v and does not consume any input.
succeedA :: b -> Analyser a b
succeedA v xs = OK (v, xs)

14

epsilonA is the trivial case of succeedA. It always succeeds and


returns the trivial value (). This implements , the symbol that
stands for an empty character sequence in grammars.
epsilonA :: Analyser a ()
epsilonA = succeedA ()
failA msg always fails with a diagnostic message msg and the
position of the next input returned.
failA :: Msg -> Analyser a b
failA msg []
= Fail (-1,-1) msg
failA msg ((_,pos):_) = Fail pos msg
errorA msg always returns an error with a diagnostic message msg
and the position of the next input.
errorA :: Msg -> Analyser a b
errorA msg []
= Error (-1,-1) msg
errorA msg ((_,pos):_) = Error pos msg
endA succeeds if there is no input left and returns the trivial value
().3
endA :: Analyser a ()
endA = total epsilonA

16.6

Elementary analyser combinators

These combinators allow the composition of analysers.


<|> is the alternation combinator. a1 <|> a2 returns the result
of a1 , or a2 if a1 failed.
( <|> ) :: Analyser a
( <|> ) a1 a2 xs
= case a1 xs of
Fail _
_
Error pos msg
OK stuff

b -> Analyser a b -> Analyser a b

-> a2 xs
-> Error pos msg
-> OK stuff

( <&> ) ::
Analyser a b -> Analyser a c -> Analyser a (b,c)
( <&> ) a1 a2 xs
= case a1 xs of
Fail pos1 msg1 -> Fail pos1 msg1
Error pos1 msg1 -> Error pos1 msg1
OK (v1,ys)
-> case a2 ys of
Fail pos2 msg2 -> Fail pos2 msg2
Error pos2 msg2 -> Error pos2 msg2
OK (v2,zs)
-> OK ((v1,v2),zs)

Analyser result modifiers

These functions modify an analyser by modifying the type of value


it returns.
a @> f changes the value returned by analyser a by applying
function f to it.
( @> ) :: Analyser a b -> (b -> c) -> Analyser a c
( @> ) a f xs
= case a xs of
Fail pos msg -> Fail pos msg
Error pos msg -> Error pos msg
OK (v,ys)
-> OK (f v, ys)
a #> v changes the value returned by analyser a by replacing it
with v.
( #> ) :: Analyser a b -> c -> Analyser a c
a #> v = a @> const v

16.8

More analyser combinators

This definition of cons as an uncurried form of : is used below.


cons :: (a,[a]) -> [a]
cons = uncurry (:)
3 Thanks

to Chris English for suggesting this strategy.

April 3, 2015

some, many, optional:: Analyser a b -> Analyser a [b]


some
a = a <&> many a @> cons
many
a = some a <|> succeedA []
optional a = a @> (: []) <|> succeedA []
someUntil a1 a2 creates an analyser that recognizes a sequence of
one or more of the things recognized by analyser a1 , like some, but
stops consuming input when a second analyser a2 would also work.
manyUntil a1 a2 creates an analyser that recognizes a sequence of
zero or more of the things recognized by analyser a1 , like many, but
stops consuming input when a second analyser a2 would also work.
someUntil, manyUntil ::
Analyser a b -> Analyser a c -> Analyser a [b]
someUntil a1 a2 input = case a2 input of
OK _ ->
failA "unexpected text" input
Fail _ _ ->
((a1 <&> manyUntil a1 a2) @> cons) input
Error _ _ ->
((a1 <&> manyUntil a1 a2) @> cons) input
manyUntil a1 a2 = someUntil a1 a2 <|> succeedA []
&> is the same as <&>, but discards the first value.
( &> ) :: Analyser a b -> Analyser a c -> Analyser a c
a1 &> a2 = a1 <&> a2 @> snd

<&> is the sequence combinator. a1 <&> a2 returns a pair formed


from the results of a1 and a2 .

16.7

some a changes analyser a which recognizes one thing into an analyser that recognizes a sequence of one or more things, returned in
a list. many a changes analyser a which recognizes one thing into
an analyser that recognizes a sequence of zero or more things, returned in a list. optional a changes analyser a which recognizes
one thing into an analyser that recognizes either zero or one things.
An empty or singleton list of things is returned.

<& is the same as <&>, but discards second value.


( <& ) :: Analyser a b -> Analyser a c -> Analyser a b
a1 <& a2 = a1 <&> a2 @> fst
alsoSat a1 a2 permits analyser a1 to succeed and consume input
iff a2 would also succeed. alsoNotSat a1 a2 permits analyser a1
to succeed and consume input iff a2 would not succeed.
alsoSat, alsoNotSat ::
Analyser a b -> Analyser a c -> Analyser a b
alsoSat a1 a2 input = case a2 input of
OK
_
-> a1 input
Fail msg pos -> Fail msg pos
Error msg pos -> Error msg pos
alsoNotSat a1 a2 [] = case a2 [] of
OK
_
-> Fail (-1,-1) "unexpected text"
Fail _
_
-> a1 []
Error _
_
-> a1 []
alsoNotSat a1 a2 ((x,p):xps) = case a2 ((x,p):xps) of
OK
_
-> Fail p "unexpected text"
Fail _
_
-> a1 ((x,p):xps)
Error _
_
-> a1 ((x,p):xps)
dataSatisfies a test permits analyser a to succeed and consume
input only if an auxiliary test performed on the data returned by a
returns True.
dataSatisfies ::
Analyser a b -> (b -> Bool) -> Analyser a b
dataSatisfies _ _ []
= Fail (-1,-1) "end of input"
dataSatisfies a test ((x,p):xps)
= case a ((x,p):xps) of
OK (dat,stuff) ->
if test dat then OK (dat, stuff)
else Fail p "unexpected text"
Fail pos msg ->
Fail pos msg
Error pos msg ->
Error pos msg
dataSatisfies a test msg permits analyser a to succeed and consume input only if an auxiliary test performed on the data returned
by a returns True. If this test fails, a Fail is returned with msg.

15

dataSatisfies ::
Analyser a b -> (b -> Bool) -> Msg -> Analyser a b
dataSatisfies _ _ _ []
= Fail (-1,-1) "end of input"
dataSatisfies a test msg ((x,p):xps)
= case a ((x,p):xps) of
OK (dat,stuff) ->
if test dat then OK (dat, stuff)
else Fail p msg
Fail pos msg ->
Fail pos msg
Error pos msg ->
Error pos msg
total a forces analyser a to fail if it cant consume all the inputs.
total :: Analyser a b ->
total a xs
= case a xs of
Error pos msg
Fail pos msg
OK (v,[])
OK (_,(_,pos):_)

Analyser a b

->
->
->
->

Error pos msg


Fail pos msg
OK (v,[])
Fail pos "unexpected text"

nofail a forces analyser a to return an error when otherwise it


would merely fail.
nofail :: Analyser a b -> Analyser a b
nofail a xs = case a xs of
Error pos msg -> Error pos msg
Fail pos msg -> Error pos msg
OK stuff
-> OK stuff
nofail a msg forces analyser a to return an error when otherwise
it would merely fail and overrides the error message with msg.
nofail :: Msg -> Analyser a b -> Analyser a b
nofail msg a xs = case a xs of
Error pos msg -> Error pos msg
Fail pos _
-> Error pos msg
OK stuff
-> OK stuff

16.9

Lexers

Lexing is the process of breaking the input stream of characters up


into a stream of lexemes (tokens). Before lexing can take place, the
locations must be added.
preLex cs returns all of the characters in cs paired with its
position.
preLex :: String -> [(Char,Pos)]
preLex = pl (0,0)
where
pl _ [] = []
pl (line,column) (\n:cs)
= (\n, (line, column)) : pl (line + 1, 0) cs
pl (line,column) (\t:cs)
= (\t, (line,column))
: pl (line, (column div 8 + 1) * 8) cs
pl (line,column) (c:cs)
= (c,
(line,column)) : pl (line, column+1) cs
Each Lexeme must be identified as belonging to one of an expected
set of classes of lexemes. This information will be passed from a
lexer to a parser by use of a Tag .
type Lexeme = String
type Tag
= String
The input to a Lexer function is a list of characters and their
positions. The output from a lexer is a list of lexemes with their
tags and positions and the list of unconsumed characters and their
positions. The output could also be an error or failure message. By
convention, a function that is a lexer has a name that ends with a
capital L.

16.10

Elementary lexers

satisfyL p tag succeeds if the first input character passes test p.


On success the lexeme returned is the string containing just that
character and the tag returned is tag. On failure the message "tag
expected." is returned.
satisfyL :: (Char -> Bool) -> Tag -> Lexer
satisfyL _ wantTag [] = failA (wantTag ++ " expected.") []
satisfyL p wantTag ((c,pos):cps)
| p c
= succeedA [((wantTag, [c]), pos)] cps
| otherwise
= failA (wantTag ++ " expected.") ((c,pos):cps)
literalL c succeeds if the first input is c. On success the lexeme
returned is [c] with the tag "c" On failure the message "c
expected." is returned.
literalL :: Char -> Lexer
literalL wantChar
= satisfyL (== wantChar) (\ : wantChar : "")
These lexers are only capable of accepting single characters. To
recognize and return tokens made up of multiple characters, we use
the combinators described below.

16.11

Special combinators for lexers

l %> tag overrides the tag produced by lexer l with tag.


( %> ) :: Lexer -> Tag -> Lexer
l %> newTag = l
@> (\[((_,lexeme),pos)] -> [((newTag,lexeme),pos)])
l1 <&&> l2 hard-sequences two lexers l1 and l2 . The tag returned is the space-separated catenation of the two tags, the lexeme returned is the catenation of the two lexemes, and the position
returned is the first position.
( <&&> ) :: Lexer -> Lexer -> Lexer
l1 <&&> l2 = (l1 <&> l2) @> f
where
f (x,[]) = x
f ([],x) = x
f ([((t,l),p)],[((t,l),_)]) =
[((t ++ " " ++ t, l ++ l), p)]
f x = error $ "<&&> non singleton lists: " ++ show x
l1 <++> l2 soft-sequences two lexers l1 and l2 . The combined
lexer returns the catenation of the lists of lexemes produced by
each Lexer.
( <++> ) :: Lexer -> Lexer -> Lexer
l1 <++> l2 = l1 <&> l2 @> uncurry (++)
l &%> tag modifies a lexer l by catenation of all its returned lexemes
and returning the supplied tag. The position returned is the first.
This permits the use of the combinators some, many and optional
(above).
( &%> ) ::
Analyser Char [[((Tag,Lexeme),Pos)]] -> Tag -> Lexer
a &%> newTag = (a @> f) %> newTag
where
f xs = [(("", concatMap (snd.fst.head) xs),
(snd.head.head) xs)]
soft (k l) returns a lexer by soft catenating the result of some
combinator k {some, many, optional, . . .} applied to a lexer l.

type Lexer = Analyser Char [((Tag,Lexeme),Pos)]

soft :: Analyser Char [[((Tag,Lexeme),Pos)]] -> Lexer


soft = ( @> concat)

Lexers produce streams of tagged lexemes. These shorthand type


synonyms, TLP and TLPs are useful when writing functions that
process lexed sources.

tagFilter tag l modifies lexer l by making it throw out lexemes


with a specified tag.

type TLP = ((Tag,Lexeme),Pos)


type TLPs = [TLP]

tagFilter :: Tag -> Lexer -> Lexer


tagFilter dropTag = ( @> filter ((/= dropTag).fst.fst))

April 3, 2015

16

16.12

Frequently used lexers

tokenL token recognizes a particular token.


tokenL :: String -> Lexer
tokenL = foldr ((<&&>) . literalL) (succeedA [])
endL succeeds at the end of input, returning no lexemes.
endL :: Lexer
endL = endA #> []
This is a common lexical structure for sources:
source ::= {$lexeme1$ | $lexeme2$ | $...$ }.
source
lexeme1
lexeme2
...

listL ls builds a lexer for source out of a list of alternative lexers


ls.
listL :: [Lexer] -> Lexer
listL =
soft . many . foldr (<|>) (failA "unexpected text")

16.13

Parsing

Parsing is the process of transforming the stream of lexemes into a


parse tree. The input to a Parser is the list of lexemes with their
tags and positions. The output from a parser is the parse tree (of
type a and the list of unconsumed lexemes, or failure or error. By
convention, a function that is a parser has a name that ends with a
capital P.
type Parser a = Analyser (Tag,Lexeme) a

16.14

Elementary parsers

tagP tag creates a parser that succeeds if the first lexeme has the
supplied tag. On failure, the message returned is "tag expected.".
tagP
tagP
tagP
|

:: Tag -> Parser (Tag,Lexeme,Pos)


wantTag [] = failA (wantTag ++ " expected.") []
wantTag (((tag,lexeme),pos):xs)
wantTag == tag
= succeedA (tag,lexeme,pos) xs
| otherwise
= failA (wantTag ++ " expected.")
(((tag,lexeme),pos):xs)

16.16

Error reporting

errMsg position message source generates an error message that


reports the error message and the offending position in the source.
warnMsg does the same, but tags the output as only a warning.
errMsg, warnMsg :: Pos -> Msg -> String -> String
errMsg = errMsg "Error"
warnMsg = errMsg "Warning"
errMsg :: String -> Pos -> Msg -> String -> String
errMsg we (-1,_) msg source =
errMsg we (sl, length (sourcelines !! sl) + 1) msg source
where
sourcelines = lines source
sl = length sourcelines -1
errMsg we _ msg [] =
we ++ ": " ++ msg ++ "\n"
errMsg we (line,col) msg source =
we ++ " on line " ++ show (line + 1) ++ ": " ++ msg
++ "\n" ++ displaylines ++ replicate col
++ "^\n" ++ replicate col ++ "|\n"
where
col | col > 0
= col
| otherwise = 0
sourcelines = lines source
displaylines
| line == 0 =
head sourcelines ++ "\n"
| line == 1 =
unlines (take 2 sourcelines)
| otherwise =
(unlines . take 3 . drop (line-2)) sourcelines

16.17

Instance declarations

instance HasPos (Could a) where


getPos c = case c of
Fail p _ -> p
Error p _ -> p
OK _
->
error "Parser.HasPos.pos: OK has no Pos."

17

Parser.Lexers

The ABR.Parser.Lexers module provides some frequently used


lexers for common syntatic elements.
module ABR.Parser.Lexers (
spaceL, tabL, vertabL, formfeedL, newlineL, returnL,
whitespaceL, dropWhite, stringL, cardinalL, fixedL,
floatL, signedCardinalL, signedFixedL, signedFloatL
) where
import Data.Char
import ABR.Parser

literalP tag lexeme creates a parser that succeeds if the first tag
and lexeme match the supplied tag and lexeme. On failure, the
message returned is "tag "lexeme" expected.".

17.1

Maintenance notes

literalP :: Tag -> Lexeme -> Parser (Tag,Lexeme,Pos)


literalP wantTag wantLex []
= failA (wantTag ++ " \"" ++ wantLex
++ "\" expected.") []
literalP wantTag wantLex (((tag,lexeme),pos):xs)
| wantTag == tag && wantLex == lexeme
= succeedA (tag,lexeme,pos) xs
| otherwise
= failA (wantTag ++ " \"" ++ wantLex
++ "\" expected.") (((tag,lexeme),pos):xs)

Reviewed
Reviewed
Reviewed
Reviewed

2015-02-03. Passed hlint.


2014-05-30: Made all the -Walls go away.
2013-11-22.
2009-04-13: Split from ABR.Parser.

17.2

Frequently used lexers

spaceL , tabL , newlineL , vertabL , formfeedL , and returnL


recognize individual whitespace characters.
space ::= " ".
space

16.15

Parser result modifiers

lineNo p extracts just the line number. p is a parser with the


same result type as tagP.
lineNo :: Parser (Tag,Lexeme,Pos) -> Parser Int
lineNo p = p @> (\(_,_,(l,_)) -> l)

April 3, 2015

tab ::= "\\t".


tab
\t

17

fixed
cardinal

newline ::= "\\n".

.
cardinal

newline
\n

fixedL :: Lexer
fixedL =
cardinalL
<&&> soft (optional (
literalL .
<&&> soft (optional cardinalL)
))
%> "fixed"

vertab ::= "\\v".


vertab
\v

formfeed ::= "\\f".


formfeed

floatL recognizes an unsigned floating point number.

\f

float ::= fixed [("e"|"E") ["+" | "-"] cardinal].

return ::= "\\r".

float

return

fixed

\r

spaceL, tabL, newlineL, vertabL, formfeedL, returnL


:: Lexer
spaceL
= literalL ;
tabL
= literalL \t
newlineL = literalL \n; vertabL
= literalL \v
formfeedL = literalL \f; returnL
= literalL \r
whitespaceL recognizes any amount of whitespace, returning it
with tag " ".
whitespace ::= {$whitespace char$}+.
whitespace
whitespace char

whitespaceL :: Lexer
whitespaceL = some (satisfyL isSpace "") &%> " "

cardinal
+

E
-

floatL :: Lexer
floatL =
fixedL
<&&> soft (optional (
(literalL e <|> literalL E)
<&&> soft (optional (
literalL - <|> literalL +
))
<&&> nofail "exponent expected." cardinalL
))
%> "float"
signedCardinalL recognizes a signed whole number.

dropWhite l modifies l by filtering out lexemes. with tag " ".


signedCardinal ::= ["-"] cardinal.

dropWhite :: Lexer -> Lexer


dropWhite = tagFilter " "
stringL recognizes strings delimited by double quotes that may
extend across many lines. Use two double quotes for one, a
` la Pascal.
string ::= "\"" {"\"\"" | $anything not \"$} "\"";
level="lexical".
string
"

"

signedCardinal
cardinal
-

signedCardinalL :: Lexer
signedCardinalL =
soft (optional (literalL -)) <&&> cardinalL
%> "signedCardinal"

""

signedFixedL recognizes a signed fixed number.


anything not "

stringL :: Lexer
stringL =
literalL "
<&&> (many (
tokenL "\"\""
<|> satisfyL (/= ") "") &%> "")
<&&> literalL "
%> "string"
cardinalL recognizes a cardinal number, a sequence of decimal
digits.
cardinal ::= {$digit$}+.
cardinal
digit

signedFixed ::= ["-"] fixed.


signedFixed
fixed
-

signedFixedL :: Lexer
signedFixedL =
soft (optional (literalL -)) <&&> fixedL
%> "signedFixed"
signedFloat ::= ["-"] float.
signedFloat
float

cardinalL :: Lexer
cardinalL = some (satisfyL isDigit "digit") &%> "cardinal"
fixedL recognizes an unsigned fractional number with no exponent.
fixed ::= cardinal ["." [cardinal]].

April 3, 2015

signedFloatL :: Lexer
signedFloatL =
soft (optional (literalL -)) <&&> floatL
%> "signedFloat"

18

18

Parser.Checks

The ABR.Parser.Checks module provides a some functions for easy


implementation of the parsing sequence.
module ABR.Parser.Checks (checkLex, checkParse) where
import ABR.Util.Pos
import ABR.Parser
import ABR.Control.Check

18.1

Maintenance notes

Reviewed 2015-02-03. Passed hlint.


Reviewed 2013-11-22.
Reviewed 2009-04-08: Separated from ABR.Parser.

18.2

Easy lexer and parser sequencing

checkLex l source uses the check abstraction to sequence the


prelexing of the source, lexing using l, error detection and construction of error messages.
checkLex ::
Lexer -> Check String [((Tag,Lexeme),Pos)] String
checkLex lexer source = case lexer (preLex source) of
Fail pos msg ->
CheckFail $ "Lexer failed.\n\n"
++ errMsg pos msg source
Error pos msg ->
CheckFail $ "Lexer error.\n\n"
++ errMsg pos msg source
OK (ls,_)
->
CheckPass ls
checkParse l p source uses the check abstraction to sequence the
prelexing of the source, lexing using l, parsing using p, error detection and construction of error messages.
checkParse :: Lexer -> Parser a -> Check String a String
checkParse lexer parser source
= case checkLex lexer source of
CheckPass tlps ->
case parser tlps of
Fail pos msg ->
CheckFail $ "Parser failed.\n\n"
++ errMsg pos msg source
Error pos msg ->
CheckFail $ "Parser error.\n\n"
++ errMsg pos msg source
OK (thing,_) ->
CheckPass thing
CheckFail msg ->
CheckFail msg

19.2

isCardinal ,

Parser.Predicates

The ABR.Parser.Predicates module provides some frequently


used predicates that depend on lexing/parsing.
module ABR.Parser.Predicates (
isCardinal, isFixed, isFloat, isSignedCardinal,
isSignedFixed, isSignedFloat
) where

isFixed ,

isFloat ,

isSignedCardinal ,

isSignedFixed and isSignedFloat are all predicated that


test whether a string could be parsed as a number.
isCardinal, isFixed, isFloat, isSignedCardinal,
isSignedFixed, isSignedFloat :: String -> Bool
isCardinal cs = case checkLex cardinalL cs of
CheckPass _ -> True
_
-> False
isFixed cs = case checkLex fixedL cs of
CheckPass _ -> True
_
-> False
isFloat cs = case checkLex floatL cs of
CheckPass _ -> True
_
-> False
isSignedCardinal cs = case checkLex signedCardinalL cs of
CheckPass _ -> True
_
-> False
isSignedFixed cs = case checkLex signedFixedL cs of
CheckPass _ -> True
_
-> False
isSignedFloat cs = case checkLex signedFloatL cs of
CheckPass _ -> True
_
-> False

20

Text.Configs

Module ABR.Text.Configs provides a type, parser and pretty


printer for a sequence of configuration settings, as might be found
in a configuration file. This kind of data could be stored in XML,
but this format is nicer to edit by hand.
module ABR.Text.Configs (
Config(..), Configs, configsL, configsP, stringL,
showConfigs, read, lookupConfig, updateConfig,
lookupParam, getParam, popTemplate
) where
import Data.Char
import Data.List
import Data.Maybe
import
import
import
import

20.1

ABR.Parser
ABR.Parser.Lexers
ABR.Text.String
ABR.Text.Showing

Maintenance notes

Reviewed 2015-02-10. Passed hlint.


Reviewed 2014-05-30: Made all the -Walls go away.
Reviewed 2013-11-22.
Reviewed 2009-04-13: Changed to ABR.Text.Configs; added design
for templates; refactored un/enString out to ABR.Text.String.

20.2

19

isNumber predicates

Language tweaks

undefined :: String -> a


undefined label = error $
"Intentional undefined at module ABR.Text.Configs, \
\ with label \"" ++ label ++ "\"."

20.3

Data types

A configuration, Config , is one of:


CFlag a flag that is set by its presence;

import ABR.Control.Check
import ABR.Parser.Checks
import ABR.Parser.Lexers

CParam a parameter with an associated value;


CSet a parameter with an associated set of configurations; or
CList a parameter with an associated list of configurations.

19.1

Maintenance notes

Reviewed 2015-02-03. Passed hlint.


Reviewed 2013-11-22.
Reviewed 2012-11-03: Split from ABR.Text.Parser.

April 3, 2015

data Config =

CFlag String
| CParam String String
| CSet String Configs
| CList String [Configs]
deriving (Eq, Ord)

19

A Configs is a list of configurations.

configsL is the lexer that will tokenize a configuration source.

type Configs = [Config]

20.4

configsL ::= {comment | name | string | "=" | "{" |"}"


| "[" | "]" | "," | $whitespace$};
level="lexical".

Lexer

Comments in configuration files start with # and extend to the end


of the line. Comments are treated as whitespace. There can be any
amount of whitespace between tokens. Aside from inside strings,
and to separate names, whitespace is not significant.

configsL
comment
name

comment ::=
"#" {$anything not \\n$} ("\\n" | $end of file$);
level="lexical".

string
=
{

comment
#
anything not \n

\n

end of file

commentL :: Lexer
commentL =
literalL #
<&&> (many (satisfyL (/= \n) "") &%> "")
<&&> (optional (literalL \n) &%> "")
%> " "
Names in configuration files may contain letters, digits, plus and
minus signs, underscores, periods, bangs and slashes. Note that a
number can lex as a name, as could many file paths. Names are case
sensitive.
name ::= {$letter$ | $digit$ |
"+" | "-" | "_" | "." | "!" | "/"}+;
level="lexical".

]
,
whitespace

configsL :: Lexer
configsL = dropWhite $ nofail $ total $ listL [
commentL, nameL, stringL,
tokenL "=" %> "symbol", tokenL "{" %> "symbol",
tokenL "}" %> "symbol", tokenL "[" %> "symbol",
tokenL "]" %> "symbol", tokenL "," %> "symbol",
whitespaceL
]

20.5

name
letter

Parser

A value is either a name or a string.

digit

value ::= name | string;


level="grammar".

+
-

value
_

name

string

valueP :: Parser String


valueP = (tagP "name" <|> tagP "string")
@> (\(_,v,_) -> v)

nameL :: Lexer
nameL = some (satisfyL nameChar "") &%> "name"
where
nameChar :: Char -> Bool
nameChar c =
isAlpha c || isDigit c || c elem "+-_.!/"

A configs is a sequence of whitespace separated configs, parsed by


configsP .
configs ::= {config};
level="grammar".

Strings are delimited by double quotes and may extend across many
lines. Use two double quotes for one, a
` la Pascal.

configs
config

string ::= "\"" {"\"\"" | $anything not \"$} "\"";


level="lexical".

configsP :: Parser Configs


configsP = many configP

string
"

"
""
anything not "

The other symbols used are:


= to bind a name to a value (either a name or a string), configuration set or configuration list;
{ to start a configuration set;

A configSet is a configs in braces.


configSet ::= "{" configs "}";
level="grammar".
configSet
{

configs

] to close a configuration list; and

configSetP :: Parser Configs


configSetP =
literalP "symbol" "{"
&> nofail "configs expected" configsP
<& nofail (literalP "symbol" "}")

, to separate items in a configuration list.

A configList is a comma separated sequence of configs in brackets.

} to close a configuration set;


[ to start a configuration list;

April 3, 2015

20

"[" "]"
| "[" configs {"," configs}+ "]"
| "[" configs "]";
level="grammar".

. showString " = [\n"


. showString (intercalate ",\n"
(map (unlines . map show) css))
. showString "]"

configList ::=

configList
[

configs

configs

showConfigs cs shows a list of configs.


showConfigs :: Configs -> String
showConfigs = concatMap ((++ "\n") . show)

configs

20.7
]

configListP :: Parser [Configs]


configListP =
literalP "symbol" "["
<&> literalP "symbol" "]"
#> []
<|>
literalP "symbol" "["
&> configsP
<&> some (
literalP "symbol" ","
&> nofail "configs expected"
configsP
)
<& nofail (literalP "symbol" "]")
@> cons
<|>
literalP "symbol" "["
&> nofail configsP
<& nofail (literalP "symbol" "]")
@> (: [])
A config is either: a binding of a name to a configSet, a
configList, or a value; or just a name.
name "=" value
| name "=" configSet
| name "=" configList
| name;
level="grammar".

Reading

read s may be used to read a parameter value s, removing quotes


first.
read :: Read a => String -> a
read = read . trim . unString

20.8
20.8.1

Accessing
configPaths

A configPath is a string that selects a Config from within some


Configs. It can be: the name of a Config, eg name; of the
form name.name to select from inside a CSet; or of the form
name!digits.name to select from inside a CList; or of combinations
like class!3.student!7.name.family.
Note this description. While the names in a Config may be any
double-quote-delimited string, those names are not useable in a configPath.

20.8.2

Lookup functions

lookupConfig n cs tries to find the named config n in cs, returning


Just the first match or Nothing. n is a configPath.

config ::=

config
name

value

name

configSet

name

configList

name

configP :: Parser Config


configP =
tagP "name"
<&> literalP "symbol" "="
<&> valueP
@> (\((_,n,_),(_,v)) -> CParam n v)
<|>
tagP "name"
<&> literalP "symbol" "="
<&> configSetP
@> (\((_,n,_),(_,cs)) -> CSet n cs)
<|>
tagP "name"
<&> literalP "symbol" "="
&> nofail configListP
@> (\((_,n,_),css) -> CList n css)
<|> tagP "name"
@> (\(_,n,_) -> CFlag n)

20.6

Showing

instance Show Config where


showsPrec _ c = case c of
CFlag n
-> showString n
CParam n v -> showString n
. showString " = " . showString v
CSet n cs
-> showString n
. showString " = {\n"
. showWithTerm "\n" cs
. showString "}"
CList n css -> showString n

April 3, 2015

lookupConfig :: String -> Configs -> Maybe Config


lookupConfig n cs = case cs of
[] ->
Nothing
CFlag n : cs
| n == n ->
Just (CFlag n)
| otherwise ->
lookupConfig n cs
CParam n v : cs
| n == n ->
Just (CParam n v)
| otherwise ->
lookupConfig n cs
CSet n cs : cs
| n == n ->
Just (CSet n cs)
| (n ++ ".") isPrefixOf n ->
lookupConfig (drop (length n + 1) n) cs
| otherwise ->
lookupConfig n cs
CList n css : cs
| n == n ->
Just (CList n css)
| (n ++ "!") isPrefixOf n ->
let (i,n) =
span isDigit (drop (length n + 1) n)
in lookupConfig (tail n) (css !! read i)
| otherwise ->
lookupConfig n cs
updateConfig n c cs tries to replace the named config n in cs with
c. n is a configPath. If the named config does not exist it will be
created. THIS IS INCOMPETELY IMPLEMENTED and is the
wrong way to build Configs anyway.
updateConfig
updateConfig
[] ->
[c] --c@(CFlag
| n ==
c :

:: String -> Config -> Configs -> Configs


n c cs = case cs of
does not handle complex names when they need
to be inserted as lists and sets
n) : cs
n ->
cs

21

| otherwise ->
c : updateConfig n c cs
c@(CParam n _) : cs
| n == n ->
c : cs
| otherwise ->
c : updateConfig n c cs
c@(CSet n cs) : cs
| n == n ->
c : cs
| (n ++ ".") isPrefixOf n ->
CSet n (updateConfig (drop (length n + 1) n) c
cs) : cs
| otherwise ->
c : updateConfig n c cs
c@(CList n css) : cs
| n == n ->
c : cs
| (n ++ "!") isPrefixOf n ->
let (i,n) =
span isDigit (drop (length n + 1) n)
(css,cs:css) = splitAt (read i) css
in CList n (css ++ updateConfig (tail n) c cs
: css) : cs
| otherwise ->
c : updateConfig n c cs
lookupParam n cs tries to find the named parameter n in cs, returning Just the first value or Nothing. (Note elem is all that is
required to test for a flag.) n is a configPath.
lookupParam :: String -> Configs -> Maybe String
lookupParam n cs = case lookupConfig n cs of
Nothing
-> Nothing
Just (CParam _ v) -> Just v
_ -> undefined "lookupParam"
getParam n cs tries to find the named parameter. It is an error if
the parameter can not be found. n is a configPath.
getParam:: String -> Configs -> String
getParam name cs =
let mp = lookupParam name cs
in fromMaybe (error $ "Missing config: " ++ name) mp

20.9

Templates

Configs are a structured data type like XML and can be used to
populate a template. The power of templates in part comes from
being able to handle variations in the data that they might get
populated with.

20.9.1

Simple Template Markup Language

A template is a String containing any kind of text, marked


up as HTML or anything else.
The hash character (#) is the special escape character in a
template. It it always treated specially.
To output a hash, use two, eg ##.
The sequence #configPath# is replaced in the output by the
value of the Config.
The output value of a Config is:
_UNDEFINED_ if the Config does not exist;
_DEFINED_ if the Config is a flag;
the unStringed text of a parameter;
_SET_ if the Config is a set; or
_LIST_ if the Config is a list.
In the following sequences, no extra whitespace is permitted.
The sequence #ifdef#configPath#text#end# outputs the text
iff configPath leads to a Config of any kind that exists, otherwise outputs nothing. The text has any template markup in
it processed as usual.

April 3, 2015

The sequence #ifundef#configPath#text#end# outputs the text


iff configPath does not lead a Config that exists, otherwise
outputs nothing. The text has any template markup in it
processed as usual.
The sequence #with#configPath#text#end# outputs the text iff
configPath leads to a set Config that exists. The text has any
template markup in it processed using the set as the new root
Configs. If the configPath does not lead to a Config that
exists, the output is _UNDEFINED_SET_. If the configPath does
not leads to a Config that exists, but is not a set, the output
is _NOT_A_SET_.
The sequence #foreach#configPath#text#end# outputs the text
for each element of the list iff configPath leads to a list
Config that exists. The text has any template markup in
it processed using each element as its root Configs. If the
configPath does not lead to a Config that exists, the output is _UNDEFINED_LIST_. If the configPath does not leads
to a Config that exists, but is not a list, the output is
_NOT_A_LIST_.

20.9.2

Populating templates

popTemplate configs template returns the text of the template populated with the data from the configs. NOT TESTED
data Mode = Quiet | Print | Reprint
popTemplate :: Configs -> String -> String
popTemplate configs = pt [(configs,Print)]
where
pt :: [(Configs,Mode)] -> String -> String
-- every #end# pops a context. in each tuple is the
-- current Configs and whether to output
pt [] "" = ""
pt [_] "" = ""
pt (_:_:_) "" = "popTemplate: not enough #end#s."
pt [] _ = error "popTemplate: too many #end#s."
pt contexts@((g,m):gvs) template = case template of
#:#:cs -> case m of
Quiet -> pt contexts cs
_
-> # : pt contexts cs
#:e:n:d:#:cs -> case m of
Reprint -> ""
_
-> pt gvs cs
#:i:f:d:e:f:#:cs ->
case span (/= #) cs of
([],_) ->
error "popTemplate: empty path (1)"
(_,[]) ->
error "popTemplate: non-terminated path (1)"
(path,#:cs) -> case m of
Quiet -> pt ((g,Quiet):contexts) cs
_
-> case lookupConfig path g of
Nothing -> pt ((g,Quiet):contexts) cs
Just _ -> pt ((g,Print):contexts) cs
_ -> undefined "pt 1"
#:i:f:u:n:d:e:f:#:cs ->
case span (/= #) cs of
([],_) ->
error "popTemplate: empty path (2)"
(_,[]) ->
error "popTemplate: non-terminated path (2)"
(path,#:cs) -> case m of
Quiet -> pt ((g,Quiet):contexts) cs
_
-> case lookupConfig path g of
Nothing -> pt ((g,Print):contexts) cs
Just _ -> pt ((g,Quiet):contexts) cs
_ -> undefined "pt 2"
#:w:i:t:h:#:cs ->
case span (/= #) cs of
([],_) ->
error "popTemplate: empty path (3)"
(_,[]) ->
error "popTemplate: non-terminated path (3)"
(path,#:cs) -> case m of
Quiet -> pt ((g,Quiet):contexts) cs
_
-> case lookupConfig path g of
Nothing -> "_UNDEFINED_SET_" ++

22

pt ((g,Quiet):contexts) cs
Just (CSet _ g) ->
pt ((g,Print):contexts) cs
Just _ -> "_NOT_A_SET_" ++
pt ((g,Quiet):contexts) cs
_ -> undefined "pt 3"
#:f:o:r:e:a:c:h:#:cs ->
case span (/= #) cs of
([],_) ->
error "popTemplate: empty path (4)"
(_,[]) ->
error "popTemplate: non-terminated path (4)"
(path,#:cs) -> case m of
Quiet -> pt ((g,Quiet):contexts) cs
_
-> case lookupConfig path g of
Nothing ->
"_UNDEFINED_LIST_" ++
pt ((g,Quiet):contexts) cs
Just (CList _ g) -> case g of
[] -> pt (([],Quiet):contexts) cs
es -> concat [pt
((e,Reprint):contexts) cs |
e <- init es] ++
pt ((last es,Print):contexts) cs
Just _ ->
"_NOT_A_List_" ++
pt ((g,Quiet):contexts) cs
_ -> undefined "pt 4"
#:cs ->
case span (/= #) cs of
([],_) ->
error "popTemplate: empty path (5)"
(_,[]) ->
error "popTemplate: non-terminated path (5)"
(path,#:cs) -> case m of
Quiet -> pt contexts cs
_
-> case lookupConfig path g of
Nothing ->
"_UNDEFINED_" ++ pt contexts cs
Just (CFlag _) ->
"_DEFINED_" ++ pt contexts cs
Just (CParam _ v) ->
unString v ++ pt contexts cs
Just (CSet _ _) ->
"_SET_" ++ pt contexts cs
Just (CList _ _) ->
"_LIST_" ++ pt contexts cs
_ -> undefined "pt 5"
c:cs -> case m of
Quiet -> pt contexts cs
_
-> c : pt contexts cs
_ -> undefined "pt 6"

21

Text.JSON

The ABR.Text.JSON library provides functions to parse, construct,


and interrogate JSON data.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
module ABR.Text.JSON (
JPropertyName, JValue(..),
jmString, jmNumber, jmTrue, jmFalse, jmNull,
jmArray, jmObject,
lexerL, valueP,
JQuery (..), JResult (..)
) where
import qualified Data.Map as M
import qualified Data.Array.IArray as A
import
import
import
import

ABR.Util.Pos
ABR.Text.Showing
ABR.Parser
ABR.Parser.Lexers hiding (stringL)

April 3, 2015

21.1

Maintenance notes

Reviewed 2015-02-10. Passed hlint.


Reviewed 2014-05-30: Added more queries, and made all the -Walls
go away.
New 2014-05-16.

21.2

Language tweaks

error :: String -> a


error label = error $
"Error in module ABR.Text.JSON, \
\ with label \"" ++ label ++ "\"."

21.3

Basic data types

A distinction must be made between string data that appears in the


JSON test (delimited with double quotes, and with special character
escaping) and string data that is ready to use in an application
(without all that). In this document, escaping is the process of
adding quotes and escaping special characters, and descaping is the
reverse process.
A String is always the string data after descaping.
A JString is a JSON string, as lexed, not yet descaped.
type JString = String
A JNumber is still a string. It has been lexed as string (without
delimiters or escaping), but awaits conversion to Int or Double.
type JNumber = String
A JPropertyName
descaped.

was lexed as a JSON string and has been

type JPropertyName = String


A JProperty binds a JPropertyName to a JValue. Useful as an
intermediate.
data JProperty = JProperty JPropertyName JValue
A JObject is a mapping from JPropertyNames to JValues.
type JObject = M.Map JPropertyName JValue
A JArray is a mapping from Intss to JValues.
type JArray = A.Array Int JValue
A JValue is a lump of JSON data.
data JValue =
JString {
jString :: String,
jPos :: Pos
}
| JNumber {
jNumber :: JNumber,
jPos :: Pos
}
| JObject {
jObject :: JObject,
jPos :: Pos
}
| JArray {
jArray :: JArray,
jPos :: Pos
}
| JTrue {
jPos :: Pos
}
| JFalse {
jPos :: Pos
}
| JNull {
jPos :: Pos
}

23

21.4

Escaping/descaping

escape cs adds the special character escapes and delimiting double


quotes.
escape :: String -> JString
escape = (" :) . (++ "\"") . concatMap (\c -> case c of
" -> "\\\""
\\ -> "\\\\"
/ -> "\\/"
\b -> "\\b"
\f -> "\\f"
\n -> "\\n"
\r -> "\\r"
\t -> "\\t"
_
-> [c]
)
-- missing hex codes

21.6

Lexer

digit19 ::= "1" | $...$ | "9"


; level="lexical".
digit19
1
...
9

digit19L :: Lexer
digit19L = satisfyL (\c -> 1 <= c && c <= 9) "digit19"
digit ::= "0" | digit19
; level="lexical".
digit
0

descape cs removes the special character escapes and delimiting


double quotes.
descape :: JString -> String
descape (" : cs) = des cs
where
des cs = case cs of
"\""
-> ""
\\ : cs -> case cs of
" : cs -> \" : des cs
\\ : cs -> \\ : des cs
/ : cs -> / : des cs
b : cs -> \b : des cs
f : cs -> \f : des cs
n : cs -> \n : des cs
r : cs -> \r : des cs
t : cs -> \t : des cs
_
->
error "descape bad escape sequence"
c : cs
-> c : des cs
[]
->
error "descape missing trailing quotes"
descape _ = error "descape missing leading quotes"
-- note, unimplemented hex codes

digit19

digitL :: Lexer
digitL =
(
literalL 0
<|> digit19L
)
%> "digit"
hexDigit ::=
digit | "a" | $...$ | "f" | "A" | $...$ | "F"
; level="lexical".
hexDigit
digit
a
...
f
A
...

21.5

Construction

The dummyPos is the fake position assigned to constructed (rather


than parsed) JSON values.
dummyPos :: Pos
dummyPos = (-666,-666)
jmString cs constructs a JSON string value containing cs.
jmString :: String -> JValue
jmString cs = JString cs dummyPos
jmNumber x constructs a JSON number value containing x .
jmNumber :: (Num a, Show a) => a -> JValue
jmNumber x = JNumber (show x) dummyPos
jmTrue constructs the JSON value true. jmFalse constructs the
JSON value false. jmNull constructs the JSON value null.
jmTrue, jmFalse, jmNull :: JValue
jmTrue = JTrue dummyPos
jmFalse = JFalse dummyPos
jmNull = JNull dummyPos
jmArray vs constructs a JSON array from vs.
jmArray :: [JValue] -> JValue
jmArray vs =
JArray (A.listArray (0,length vs - 1) vs) dummyPos
jmObject nvs constructs a JSON object from nvs, where nvs is a
list of pairs, (n, v), n is a property name, and v is its value.
jmObject :: [(JPropertyName,JValue)] -> JValue
jmObject nvs = JObject (M.fromList nvs) dummyPos

April 3, 2015

hexDigitL :: Lexer
hexDigitL =
(
digitL
<|> satisfyL (\c -> a <= c && c <= f ||
A <= c && c <= F) ""
)
%> "hexDigit"
string ::=
"\""
{<$any character$ ! "\""| "\\"> | escapeSequence}
"\""
; level="lexical".
string
"

"
any character
not
"
\
escapeSequence

stringL :: Lexer
stringL =
literalL \"
<&&> (many (
satisfyL (notElem "\"\\") ""
<|> escapeSequenceL
) &%> "")
<&&> nofail (literalL \")
%> "string"

24

separator
,

escapeSequence ::= "\\"


("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t" |
hexDigit hexDigit hexDigit hexDigit)
; level="lexical".

:
{
}

escapeSequence
\

"

\
/
b
f
n
r
t
hexDigit

hexDigit

hexDigit

hexDigit

escapeSequenceL :: Lexer
escapeSequenceL =
literalL \\
<&&> (
literalL \"
<|> literalL \\
<|> literalL /
<|> literalL b
<|> literalL f
<|> literalL n
<|> literalL r
<|> literalL t
<|> hexDigitL <&&> hexDigitL <&&> hexDigitL <&&>
hexDigitL
)
%> "excapeSequence"
number ::= ["-"] ("0" | digit19 {digit}) ["." {digit}] \
[("e" | "E") ($epsilon$ | "+" | "-") {digit}+]
; level="lexical".

separatorL :: Lexer
separatorL =
(
literalL ,
<|> literalL :
<|> literalL {
<|> literalL }
<|> literalL [
<|> literalL ]
)
%> "separator"
keyword ::= "true" | "false" | "null"
; level="lexical".

keyword
true
false
null

keywordL :: Lexer
keywordL =
(
tokenL "true"
<|> tokenL "false"
<|> tokenL "null"
)
%> "keyword"
lexerL lexes a JSON source.

number
0
-

digit19
digit

e
E

digit

lexer ::= {$whitespace$ | separator | string |


number | keyword}
; level="lexical".

digit
lexer

whitespace

separator

numberL :: Lexer
numberL =
soft (optional (literalL -))
<&&> (
literalL 0
<|> digit19L <&&> (many digitL &%> "")
)
<&&> soft (optional (
literalL .
<&&> (many digitL &%> "")
))
<&&> soft (optional (
(literalL e <|> literalL E)
<&&> soft (optional (literalL + <|>
literalL -))
<&&> soft (some digitL)
))
%> "number"

string
number
keyword

lexerL :: Lexer
lexerL = dropWhite $ nofail $ total $ listL [whitespaceL,
separatorL, stringL, numberL, keywordL]

21.7

Parser

valueP parses a JSON source.


value ::=

separator ::= "," | ":" | "{" | "}" | "[" | "]"


; level="lexical".

April 3, 2015

string | number | object | array


| "true" | "false" | "null"
; level="grammar".

25

value

<&> optional (
valueP
<&> many (
literalP "separator" ","
&> nofail "value expected" valueP
)
@> cons
)
<& nofail (literalP "separator" "]")
@> (\((_,_,p), vss) -> JArray {
jArray = let vs = concat vss
in A.listArray (0, length vs - 1) vs,
jPos = p
})

string
number
object
array
true
false
null

valueP :: Parser JValue


valueP =
tagP "string"
@> (\(_,l,p) -> JString (descape l) p)
<|> tagP "number"
@> (\(_,l,p) -> JNumber l p)
<|> objectP
<|> arrayP
<|> literalP "keyword" "true"
@> (\(_,_,p) -> JTrue p)
<|> literalP "keyword" "false"
@> (\(_,_,p) -> JFalse p)
<|> literalP "keyword" "null"
@> (\(_,_,p) -> JNull p)

21.8

Interrogation

A JQuery is a request to project out a part of a JValue. It has the


following values:

object ::= "{" [property {"," property}] "}"


; level="grammar".

JQStr The value is a string, return it as a String.

JQInt The value is a number, return it as an Int.

JQDbe The value is a number, return it as a Double.

JQBool The value is true or false, return it as a Bool.

JQProp n The value is an object, return the property named


n.

JQProps The value is an object, return all of the the properties as a list of (name, value) pairs.

object
{

JQElem i The value is an array, return the ith element.

property
,

property

objectP :: Parser JValue


objectP =
literalP "separator" "{"
<&> optional (
propertyP
<&> many (
literalP "separator" ","
&> nofail "property expected" propertyP
)
@> cons
)
<& nofail (literalP "separator" "}")
@> (\((_,_,p), pss) -> JObject {
jObject = M.fromList
(map (\(JProperty n v) -> (n, v)) (concat pss)),
jPos = p
})
property ::= string ":" value
; level="grammar".

JQElems The value is an array, return all the elements, in


order, in a list; or the value is an object, return all the property
values.
JQIsNull The value might be null, return True if it is.

q1 :-> q2 This query is really a sequence of two queries to


be applied in the order q1 then q2 .
infixl 9 :->
data JQuery =
JQStr
| JQInt
| JQDbe
| JQBool
| JQProp JPropertyName
| JQProps
| JQElem Int
| JQElems
| JQIsNull
| JQuery :-> JQuery
Class JResult overloads jGet.
jGet q v applies the query q to value v. It may fail with Nothing.

property
string

class JResult a where


jGet :: JQuery -> JValue -> Maybe a

value

propertyP :: Parser JProperty


propertyP =
tagP "string"
<&> nofail (literalP "separator" ":")
&> nofail "value expected" valueP
@> (\((_,l,_),v) -> JProperty (descape l) v)

array
]
value
,

arrayP :: Parser JValue


arrayP =
literalP "separator" "["

April 3, 2015

value

21.9.1

Instances
Showing

The Show instances reconstruct valid JSON syntax.


instance Show JProperty where
showsPrec _ (JProperty k v) =
showString (escape k) .
showString " : " .
shows v

array ::= "[" [value {"," value}] "]"


; level="grammar".

21.9

instance Show JValue where


showsPrec _ v = case v of
JString cs _ ->
showString (escape cs)
JNumber cs _ ->
showString cs
JObject obj _ ->

26

showString "{\n" .
showWithSep ",\n"
(map (uncurry JProperty) (M.toList obj)) .
showString "\n}"
JArray arr _ ->
showString "[\n" .
showWithSep ",\n" (A.elems arr) .
showString "\n]"
JTrue
_ ->
showString "true"
JFalse
_ ->
showString "false"
JNull
_ ->
showString "null"

22.1

Maintenance notes

New 2014-12-29: Started to support marks entry via Blackboard


Marks centre.

22.2

Basic data types

A CSV file is one big string.


type CSV = String
A Field is one cell.
type Field = String
A Row is one list of Fields.
type Row = [Field]

21.9.2

Interrogation

A Table is a just list of Rows.

instance JResult String where


jGet JQStr (JString cs _) = Just cs
jGet (q1 :-> q2) v = case jGet q1 v of
Nothing -> Nothing
Just v -> jGet q2 v
jGet _ _ = Nothing

type Table = [Row]

instance JResult Int where


jGet JQInt (JNumber cs _) = Just (read cs)
jGet (q1 :-> q2) v = case jGet q1 v of
Nothing -> Nothing
Just v -> jGet q2 v
jGet _ _ = Nothing

22.4

Interrogation

22.5

Instances

22.3

Parser

Dont really need a full-on parser.

22.5.1

Showing

The Show instances reconstruct valid CSV syntax.


instance JResult Double where
jGet JQDbe (JNumber cs _) = Just (read cs)
jGet (q1 :-> q2) v = case jGet q1 v of
Nothing -> Nothing
Just v -> jGet q2 v
jGet _ _ = Nothing
instance JResult Bool where
jGet JQBool (JTrue _) = Just True
jGet JQBool (JFalse _) = Just False
jGet JQIsNull (JNull _) = Just True
jGet JQIsNull _ = Just False
jGet (q1 :-> q2) v = case jGet q1 v of
Nothing -> Nothing
Just v -> jGet q2 v
jGet _ _ = Nothing
instance JResult JValue where
jGet (JQProp k) (JObject m _) = M.lookup k m
jGet (JQElem i) (JArray a _) =
let (l,h) = A.bounds a
in if l <= i && i <= h then Just (a A.! i)
else Nothing
jGet (q1 :-> q2) v = case jGet q1 v of
Nothing -> Nothing
Just v -> jGet q2 v
jGet _ _ = Nothing
instance JResult [JValue] where
jGet JQElems (JObject m _) = Just (M.elems m)
jGet JQElems (JArray a _) = Just (A.elems a)
jGet (q1 :-> q2) v = case jGet q1 v of
Nothing -> Nothing
Just v -> jGet q2 v
jGet _ _ = Nothing
instance JResult [(JPropertyName,JValue)] where
jGet JQProps (JObject m _) = Just (M.assocs m)
jGet (q1 :-> q2) v = case jGet q1 v of
Nothing -> Nothing
Just v -> jGet q2 v
jGet _ _ = Nothing

22

Text.CSV

The ABR.Text.CSV library provides functions to parse, construct,


and interrogate CSV data.
module ABR.Text.CSV where

April 3, 2015

22.5.2

23

Interrogation

Text.Markup

Module ABR.Text.Markup is a collection of functions that operate


on strings wrt to Markup Languages.
module ABR.Text.Markup (
encodeHTML, encodeHTML, makeHTMLSafe, makeHTMLSafe,
makeLatexSafe, latex2html
) where
import Data.Char

23.1

Maintenance notes

Reviewed 2014-05-30: Made all the -Walls go away.


Reviewed 2013-11-22.
Reviewd 2009-04-13: Split off from ABR.Text.String.

23.2

Making text safe for HTML

encodeHTML c returns cs special character encoding if c {<, >,


&, "}, otherwise c. encodeHTML c returns cs special character
encoding, additionally encoding all control characters.
encodeHTML, encodeHTML :: Char -> String
encodeHTML c = case c of
< -> "&lt;"
> -> "&gt;"
& -> "&amp;"
" -> "&quot;"
_
-> [c]
encodeHTML c = case c of
< -> "&lt;"
> -> "&gt;"
& -> "&amp;"
" -> "&quot;"
_
-> if isControl c then
"&#" ++ show (fromEnum c) ++ ";"
else
[c]
makeHTMLSafe cs encodes all of the special characters in cs. It
would be counter productive to put text containing tags through
this filter. makeHTMLSafe cs encodes all of the special characters
in cs including all control characters.

27

makeHTMLSafe, makeHTMLSafe :: String -> String


makeHTMLSafe = concatMap encodeHTML
makeHTMLSafe = concatMap encodeHTML

23.3

Making text safe for LaTeX

makeLatexSafe cs makes cs safe for inclusion in a LATEX document


as plain text, by encoding some special characters.
makeLatexSafe :: String -> String
makeLatexSafe = concatMap safe . unwords . words
where
safe :: Char -> String
safe c = case c of
$ -> "\\$"
% -> "\\%"
_ -> "\\_"
} -> "\\}"
& -> "\\&"
# -> "\\#"
{ -> "\\{"
^ -> "\\textasciicircum "
~ -> "\\textasciitilde "
* -> "\\textasteriskcentered "
\\ -> "\\textbackslash "
| -> "\\textbar "
> -> "\\textgreater "
< -> "\\textless "
_
-> [c]

23.4

Converting LaTeX to HTML

latex2html cs converts LATEX string cs to HTML. This is not


meant for whole documents. It has some basics for writing comments just like this one. This is used by mashdoc.
latex2html :: String -> String
latex2html = t2h [""]
t2h ends cs converts LATEX string cs to HTML. ends is a list of
element closes awaiting the next }. We are currently in text mode.
t2h :: [String] -> String -> String
t2h ends source = case source of
{ : cs ->
t2h ("" : ends) cs
} : cs ->
case ends of
[]
-> t2h [] cs
end : ends -> end ++ t2h ends cs
$ : cs ->
m2h ends cs
~ : cs ->
"&nbsp;" ++ t2h ends cs
: : cs ->
"&ldquo;" ++ t2h ends cs
\ : \ : cs ->
"&rdquo;" ++ t2h ends cs
" : cs ->
"&rdquo;" ++ t2h ends cs
\\ : cs ->
case cs of
\\ : cs ->
"<br />" ++ t2h ends cs
$ : cs ->
$ : t2h ends cs
{ : cs ->
{ : t2h ends cs
: cs ->
: t2h ends cs
_ ->
let (command, rest) = getCommand cs
in case command of
"textbar" -> "|" ++ t2h ends rest
"tt"
-> "<tt>" ++ t2h "</tt>" ends rest
"it"
-> "<i>" ++ t2h "</i>" ends rest
"bf"
-> "<b>" ++ t2h "</b>" ends rest
"verb" -> case rest of
""
-> t2h ends ""
[r]
-> t2h ends [r]

April 3, 2015

r : rs ->
let (vs, rs) = break (== r) rs
in "<tt>" ++ makeHTMLSafe vs ++
"</tt>" ++ t2h ends (drop 1 rs)
_
-> t2h ends rest
-- MORE CASES GO HERE
c : cs ->
c : t2h ends cs
[] ->
concat ends
t2h newEnd ends cs converts LATEX string cs to HTML. ends is a
list of element closes awaiting the next }. We are currently in text
mode. newEnd is a new closing tag to add to the innermost ending.
t2h :: String -> [String]
t2h newEnd ends cs = case
[]
-> t2h [newEnd]
e : es -> t2h ((newEnd

-> String -> String


ends of
cs
++ e) : es) cs

m2h ends cs converts LATEX string cs to HTML. ends is a list of


element closes awaiting the next }. We are currently in math mode.
m2h :: [String] -> String -> String
m2h ends source = case source of
{ : cs ->
m2h ("" : ends) cs
} : cs ->
case ends of
[]
-> m2h [] cs
end : ends -> end ++ m2h ends cs
$ : cs ->
t2h ends cs
< : cs ->
" &lt; " ++ m2h ends cs
> : cs ->
" &gt; " ++ m2h ends cs
- : cs ->
" &minus; " ++ m2h ends cs
^ : cs ->
case cs of
{ : cs ->
"<sup>" ++ m2h "</sup>" ends cs
c : cs ->
"<sup>" ++ m2h ends [c] ++ "</sup>"
++ m2h ends cs
[] ->
[]
_ : cs ->
case cs of
{ : cs ->
"<sub>" ++ m2h "</sub>" ends cs
c : cs ->
"<sub>" ++ m2h ends [c] ++ "</sub>"
++ m2h ends cs
[] ->
[]
\\ : cs ->
case cs of
, : cs ->
: m2h ends cs
$ : cs ->
$ : m2h ends cs
{ : cs ->
{ : m2h ends cs
: cs ->
: m2h ends cs
_ ->
let (command, rest) = getCommand cs
in case command of
"alpha"
-> "<i>&alpha;</i>" ++
m2h ends rest
"beta"
-> "<i>&beta;</i>" ++
m2h ends rest
"gamma"
-> "<i>&gamma;</i>" ++
m2h ends rest
"delta"
-> "<i>&delta;</i>" ++
m2h ends rest
"epsilon" -> "<i>&epsilon;</i>" ++
m2h ends rest
"zeta"
-> "<i>&zeta;</i>" ++

28

"eta"
"theta"
"iota"
"kappa"
"lambda"
"mu"
"nu"
"xi"
"omicron"
"pi"
"rho"
"sigma"
"tau"
"upsilon"
"phi"
"chi"
"psi"
"omega"
"Alpha"
"Beta"
"Gamma"
"Delta"
"Epsilon"
"Zeta"
"Eta"
"Theta"
"Iota"
"Kappa"
"Lambda"
"Mu"
"Nu"
"Xi"
"Omicron"
"Pi"
"Rho"
"Sigma"
"Tau"
"Upsilon"
"Phi"

April 3, 2015

m2h ends rest


-> "<i>&eta;</i>" ++
m2h ends rest
-> "<i>&theta;</i>" ++
m2h ends rest
-> "<i>&iota;</i>" ++
m2h ends rest
-> "<i>&kappa;</i>" ++
m2h ends rest
-> "<i>&lambda;</i>" ++
m2h ends rest
-> "<i>&mu;</i>" ++
m2h ends rest
-> "<i>&nu;</i>" ++
m2h ends rest
-> "<i>&xi;</i>" ++
m2h ends rest
-> "<i>&omicron;</i>" ++
m2h ends rest
-> "<i>&pi;</i>" ++
m2h ends rest
-> "<i>&rho;</i>" ++
m2h ends rest
-> "<i>&sigma;</i>" ++
m2h ends rest
-> "<i>&tau;</i>" ++
m2h ends rest
-> "<i>&upsilon;</i>" ++
m2h ends rest
-> "<i>&phi;</i>" ++
m2h ends rest
-> "<i>&chi;</i>" ++
m2h ends rest
-> "<i>&psi;</i>" ++
m2h ends rest
-> "<i>&omega;</i>" ++
m2h ends rest
-> "&Alpha;" ++
m2h ends rest
-> "&Beta;" ++
m2h ends rest
-> "&Gamma;" ++
m2h ends rest
-> "&Delta;" ++
m2h ends rest
-> "&Epsilon;" ++
m2h ends rest
-> "&Zeta;" ++
m2h ends rest
-> "&Eta;" ++
m2h ends rest
-> "&Theta;" ++
m2h ends rest
-> "&Iota;" ++
m2h ends rest
-> "&Kappa;" ++
m2h ends rest
-> "&Lambda;" ++
m2h ends rest
-> "&Mu;" ++
m2h ends rest
-> "&Nu;" ++
m2h ends rest
-> "&Xi;" ++
m2h ends rest
-> "&Omicron;" ++
m2h ends rest
-> "&Pi;" ++
m2h ends rest
-> "&Rho;" ++
m2h ends rest
-> "&Sigma;" ++
m2h ends rest
-> "&Tau;" ++
m2h ends rest
-> "&Upsilon;" ++
m2h ends rest
-> "&Phi;" ++

m2h ends rest


-> "&Chi;" ++
m2h ends rest
"Psi"
-> "&Psi;" ++
m2h ends rest
"Omega"
-> "&Omega;" ++
m2h ends rest
"le"
-> " &le; " ++
m2h ends rest
"ge"
-> " &ge; " ++
m2h ends rest
"times"
-> " &times; " ++
m2h ends rest
"pm"
-> " &plusmn; " ++
m2h ends rest
"mathit" -> m2h ends rest
_
-> m2h ends rest
-- MORE CASES GO HERE
c : cs
| isAlpha c ->
"<i>" ++ c : "</i>" ++ m2h ends cs
| otherwise ->
c : m2h ends cs
[] ->
[]
"Chi"

m2h newEnd ends cs converts LATEX string cs to HTML. ends is a


list of element closes awaiting the next }. We are currently in mayj
mode. newEnd is a new closing tag to add to the innermost ending.
m2h :: String -> [String]
m2h newEnd ends cs = case
[]
-> m2h [newEnd]
e : es -> m2h ((newEnd

-> String -> String


ends of
cs
++ e) : es) cs

getCommand cs splits a string into the first run of letters and the
rest. A space at the end of the run of letters is discarded (as per
LATEX).
getCommand :: String -> (String, String)
getCommand cs = case cs of
"" ->
("", "")
: cs ->
("", cs)
c : cs
| isAlpha c ->
let (command, rest) = getCommand cs
in (c : command, rest)
| otherwise ->
("", c : cs)

24

Text.String

Module ABR.Text.String is a collection of functions that operate


on strings.
module ABR.Text.String (
wordWrap, lJustify, rJustify, lJustify, rJustify,
justifyColumn, makeTable, spaceColumns, makeTableL,
makeTableMR, fields, unfields, trim, nameCmp, nameLT,
fixNewlines, fixNewlines, spaces, findClosest,
(++/++), (++.++), catenateWith, substs, subst,
subHashNums, subHashNames, unString, enString
) where
import Data.List
import Data.Char

24.1

Maintenance notes

Reviewed
Reviewed
Reviewed
Reviewed

2015-02-10. Passed hlint.


2014-05-30: Made all the -Walls go away.
2013-11-22.
2009-04-13: Changed to ABR.Text.String.

24.2

Word wrapping

wordWrap width cs wraps the words in cs to no wider than width,


unless a word is wider than width, returning a list of lines.

29

wordWrap :: Int -> String -> [String]


wordWrap width = wrap 0 [] . words
where
wrap :: Int -> String -> [String] -> [String]
wrap 0 [] []
= []
wrap _ cs []
= [cs]
wrap n cs (w : ws) =
let n = length w
n = n + 1 + n
in if n == 0 then
wrap n w ws
else if n <= width then
wrap n (cs ++ : w) ws
else
cs : wrap n w ws

24.3

Justification

makeTableL c cols makes a table from cols using all left justification, with c used to pad columns and separate columns.
makeTableL :: Char -> [[String]] -> String
makeTableL c cols = makeTable (repeat (lJustify c)) $
spaceColumns [c] cols
makeTableMR js rows makes a table from elements that are themselves multi-rowed. js is a list of justifiers for each column (as in
makeTable. rows is a list of rows, where each row is a list of columns.
makeTableMR :: [Int -> String -> String] ->
[[[String]]] -> String
makeTableMR js =
let padcs cols =
let h = maximum $ map length cols
pad col = col ++ replicate (h - length col) ""
in map pad cols
in makeTable js . map concat . transpose . map padcs

lJustify w cs pads cs with extra spaces on the right to make the


overall width not less than w. rJustify w cs pads cs with extra
spaces on the left to make the overall width not less than w.
lJustify, rJustify :: Int -> String -> String
lJustify = lJustify
rJustify = rJustify
lJustify p w cs pads cs with extra pad characters p on the right
to make the overall width not less than w. rJustify p w cs pads
cs with extra pad characters p on the left to make the overall width
not less than w.
lJustify, rJustify :: Char -> Int -> String -> String
lJustify p w cs =
let lcs = length cs
in if lcs >= w
then cs
else cs ++ replicate (w - lcs) p
rJustify p w cs =
let lcs = length cs
in if lcs >= w
then cs
else replicate (w - lcs) p ++ cs

24.4

Tables with justified columns

justifyColumn j col justifies all of the strings in col using j to


justify them all to the same width, which is the width of the widest
string in col.
justifyColumn :: (Int -> String -> String) ->
[String] -> [String]
justifyColumn j css = case css of
[] -> []
_ ->
let w = maximum $ map length css
in map (j w) css
makeTable js cols applies the justification functions in js to the
corresponding columns in cols and assembles the final table. Short
columns have extra blank rows added at the bottom.
makeTable :: [Int -> String -> String] ->
[[String]] -> String
makeTable js css = case css of
[] -> "\n"
_ ->
let h = maximum $ map length css
pad col = col ++ replicate (h - length col) ""
in unlines $ map concat $ transpose $
zipWith justifyColumn js $ map pad css
spaceColumns cs cols spaces out columns cols by inserting columns
of replicated strings cs.
spaceColumns :: String -> [[String]] -> [[String]]
spaceColumns cs css = case css of
[] -> []
_ ->
let h = maximum $ map length css
spaceCol = replicate h cs
in intersperse spaceCol css

April 3, 2015

24.5

Fields

These are functions for breaking a string into a list of fields and
converting a list of fields into a string. The fields are delimited with
a nominated special character. To permit the special character to
appear in a field it is preceded by a nominated escape character. To
permit the escape character to appear in a string, it is preceded by
itself.
fields d e cs breaks string cs into a list of strings at each delimit
character d, removing escape characters e where appropriate. If the
ecsaping is not required use ABR.Data.List.chop instead.
fields :: Char -> Char -> String -> [String]
fields d e cs = case cs of
[] -> []
_ -> f [] [] cs
where
f css rs cs = case cs of
[] ->
reverse (reverse rs : css)
[c]
| c == d ->
reverse ([] : reverse rs : css)
| otherwise ->
reverse (reverse (c:rs) : css)
c:c:cs
| c == e && c == d ->
f css (d:rs) cs
| c == e && c == e ->
f css (e:rs) cs
| c == d ->
f (reverse rs : css) [] (c:cs)
| otherwise ->
f css (c:rs) (c:cs)
unfields d e css converts css into one string, with each field separated by the delimit character d, adding escape characters e as
needed. If the ecsaping is not required use Data.List.intersperse
instead.
unfields :: Char -> Char -> [String] -> String
unfields d e css = case css of
[]
-> []
[cs]
-> escape cs
cs:cs:css -> escape cs ++ [d]
++ unfields d e (cs:css)
where
escape :: String -> String
escape []
= []
escape (c:cs)
| c == d
= e : d : escape cs
| c == e
= e : e : escape cs
| otherwise = c : escape cs

24.6

Whitespace

trim cs strips any whitespace from both ends of cs.


trim :: String -> String
trim = reverse . dropWhile isSpace . reverse
. dropWhile isSpace

30

fixNewlines cs rectifies the ends of lines in cs. It does not ensure


that the last character is a newline.
fixNewlines :: String -> String
fixNewlines cs = case cs of
\r : \n : cs -> \n : fixNewlines cs
\r : cs
-> \n : fixNewlines cs
c : cs
-> c : fixNewlines cs
[]
-> []
fixNewlines cs rectifies the ends of lines in cs. This version
ensures that the last line is complete, i.e. that unless cs is empty,
the last character returned will be a newline.
fixNewlines :: String -> String
fixNewlines cs = case cs of
"\n"
-> "\n"
"\r"
-> "\n"
"\r\n"
-> "\n"
[c]
-> c : "\n"
\r : \n : cs -> \n : fixNewlines cs
\r : cs
-> \n : fixNewlines cs
c : cs
-> c : fixNewlines cs
[]
-> []
spaces n returns n spaces.
spaces :: Int -> String
spaces n = replicate n

24.8

Names

nameCmp n1 n2 orders n1 and n2 . Use this to sort names with


sortBy when names are in family-name comma other-names format.
nameCmp :: String -> String -> Ordering
nameCmp xs ys
= compare (f xs) (f ys)
where
f = map toUpper . filter (notElem " -")
nameLT n1 n2 returns True if name n1 < n2 . Use this to sort
names with msort when names are in family-name comma othernames format.
nameLT :: String -> String -> Bool
nameLT xs ys
= f xs < f ys
where
f = map toUpper . filter (notElem " -")

24.9

Path catenation operators

++/++ joins two paths with a single /. ++.++ joins two paths with
a single . The utility of these operators is that any extra /s or .s
at the join are removed.
infixl 6 ++/++, ++.++

24.7

Pattern matching and substitution

findClosest pattern candiates returns the position in candidates


of the string which, ignoring case is closest to pattern or 1 if
candidates is empty.
findClosest :: String -> [String] -> Int
findClosest pattern candidates
= fc (-1) (-1) (-1) $ map (map toUpper) candidates
where
p = map toUpper pattern
fc _ pos _ []
= pos
fc run pos n (c:cs)
| run >= run = fc run pos (n+1) cs
| otherwise
= fc run (n+1) (n+1) cs
where
run =
length $ takeWhile (uncurry (==)) $ zip c p
substs prs cs performs substitutions on cs. prs is a list of pairs
(p, r), where p is a case sensitive pattern to be replaced by r wherever
it occurs.
substs :: [(String, String)] -> String -> String
substs prs = s prs
where
s _
[]
= []
s []
(c:cs)
= c : s prs cs
s ((p,r):prs) cs
| p isPrefixOf cs =
r ++ s prs (drop (length p) cs)
| otherwise
= s prs cs
subst p r cs performs substitutions on cs. p is a case sensitive
pattern to be replaced by r wherever it occurs.
subst :: String -> String -> String -> String
subst p r = substs [(p,r)]
subHashNums rs cs performs substitutions on cs. rs is a list of
replacements. r!!0 will replace the pattern #0#, r!!1 will replace
the pattern #1#, ...
subHashNums :: [String] -> String -> String
subHashNums rs = substs (zipWith make rs [0..])
where
make :: String -> Int -> (String, String)
make r n = (# : show n ++ "#", r)
subHashNames nrs cs performs substitutions on cs. nrs is a list of
of pairs (n, r), where n is a case sensitive name to be replaced by r
wherever it occurs between a pair of hashes.
subHashNames :: [(String,String)] -> String -> String
subHashNames nrs =
substs $ map (\(n,r) -> (# : n ++ "#", r)) nrs

April 3, 2015

(++/++), (++.++) :: String -> String -> String


(++/++) = catenateWith /
(++.++) = catenateWith .
More such operators can be constructed with
catenateWith c cs cs 0 , which catenates cs and cs 0 with exactly
one c at the join.
catenateWith :: Char -> String -> String -> String
catenateWith c cs cs =
reverse (dropWhile (== c) (reverse cs))
++ [c]
++ dropWhile (== c) cs

24.10

Simple String Delimitation

unString s rectifies string s, by removing the double quotes from


each end (if present) and replacing pairs of double quotes with just
one. If there are no double quotes in s, it is returned unchanged.
unString :: String -> String
unString s = case s of
[]
-> []
"\""
-> []
"\"\""
-> []
" : " : cs -> " : unString cs
" : cs
-> unString cs
c : cs
-> c : unString cs
enString s encodes a string with enclosing quotes and doubles any
enclosed quotes.
enString :: String -> String
enString s = " : dbl s ++ "\""
where
dbl cs = case cs of
[]
-> []
":cs -> "\"\"" ++ dbl cs
c :cs -> c : dbl cs

25

Text.Showing

The ABR.Text.Showing library provides functions to help write


new instances of class Show, and to get control of numeric precision.
module ABR.Text.Showing (
showWithSep, showWithTerm, FormattedDouble(..),
makeFormattedDouble, showFD
) where
import Numeric

31

25.1

Maintenance notes

26.1

Reviewed
Reviewed
Reviewed
Reviewed

2015-02-10. Passed hlint.


2014-05-30: Made all the -Walls go away.
2013-11-22.
2012-11-24: Moved into ABR.Text.

Reviewed 2013-11-24.
Reviewed 2012-11-23: Saved from the CDL project for RL/PL.

25.2

Language tweaks

An Kind is the essential structure of the elements of a type. Only


types of the same kind may be combined in certain ways, for example
union. The traditional kind of type is the one that consist of named
constants ( KNamed ). Types may also be subsets of the integers
( KIntegral ), or sets of strings ( KString ). Cartesian products of

error :: String -> a


error label = error $
"Error in module ABR.Text.Showing, \
\ with label \"" ++ label ++ "\"."

25.3

Adding Delimiters

showWithSep sep xs shows the elements of xs separated by sep.

26.2

25.4

Controlling Precision

A FormattedDouble is a Double bound to a desired format. The


format is one of: FD for no exponent; ED for an exponent; or GD
for the best choice between the two. An optional integer specifies
the number of digits after the decimal point.
data FormattedDouble =

FD (Maybe Int) Double


| ED (Maybe Int) Double
| GD (Maybe Int) Double

This Show instance applies the formatting to the Double.


instance Show FormattedDouble where
showsPrec _ fd = case fd
FD md x -> showFFloat
ED md x -> showEFloat
GD md x -> showGFloat

of
md x
md x
md x

makeFormattedDouble format x makes a FormattedDouble from a


Double x and a string that describes the format, format, of the form
("f" | "e" | "g") {digit}, e.g. "f2".
makeFormattedDouble :: String -> Double -> FormattedDouble
makeFormattedDouble format = case format of
"f"
-> FD Nothing
"e"
-> ED Nothing
"g"
-> GD Nothing
f:cs -> FD (Just (read cs))
e:cs -> ED (Just (read cs))
g:cs -> GD (Just (read cs))
_
->
error "makeFormattedDouble bad format"
showFD format x shows x using the format described by format.
showFD :: String -> Double -> String
showFD format x = show (makeFormattedDouble format x)

26

Logic.Kinds

Module Kinds implements kinds for arguments in logic atoms.


module ABR.Logic.Kinds (
Kind(..), HasKind(..), kindCheckList, kUnify
) where
import Data.Maybe
import ABR.Util.Pos
import ABR.Text.Showing

April 3, 2015

Data type

types yield tuple kinds ( KTuple ). There should never be less than
two elements in a tuple kind. A with variables in it has an unknown
kind ( KUnknown ).
data Kind =

showWithTerm term xs shows the elements of xs terminated by


terminator term. (Adapted from Mark Joness Mini Prolog.)
showWithSep, showWithTerm ::
Show a => String -> [a] -> ShowS
showWithSep s xs = case xs of
[]
-> id
[x]
-> shows x
(x:xs) ->
shows x . showString s . showWithSep s xs
showWithTerm s xs = case xs of
[] -> id
xs -> foldr1 (.) [shows x . showString s | x <- xs]

Maintenance notes

KNamed
| KIntegral
| KString
| KTuple [Kind]
| KUnknown
deriving (Eq, Ord)

Kinds are not declared. They are inferred. So no parser is necessary.

26.3

Unification

kUnify k k0 returns the unification of k and k0 if there is one.


kUnify :: Kind -> Kind -> Maybe Kind
kUnify k k = case k of
KNamed
-> case k of
KNamed
-> Just KNamed
KUnknown
-> Just KNamed
_
-> Nothing
KIntegral -> case k of
KIntegral -> Just KIntegral
KUnknown
-> Just KIntegral
_
-> Nothing
KString
-> case k of
KString
-> Just KString
KUnknown
-> Just KString
_
-> Nothing
KTuple ks -> case k of
KTuple ks ->
let n = length ks
ks = catMaybes $ zipWith kUnify ks ks
in if n == length ks && n == length ks
then Just $ KTuple ks
else Nothing
KUnknown
-> Just $ KTuple ks
_
-> Nothing
KUnknown -> Just k

26.4

Kind inference

Class HasKind overload functions pertaining to kinds.


class (Show a, HasPos a) => HasKind a where
kindCheck x infers the kind of x.
kindCheck :: a -> IO Kind
kindCheck x = ioError $ userError $
"Unimplemented kindCheck for thing:\n"
++ show x ++ "\nat " ++ show (getPos x)
kindCheckList xs infers the unified kind of xs.
length of xs 2.

precondition:

kindCheckList :: HasKind a => [a] -> IO Kind


kindCheckList xs = do
ks <- mapM kindCheck xs
let fold :: HasKind a => [(Kind, a)] -> IO Kind
fold ((k,x):(k,x):kxs) = case kUnify k k of
Just k -> if null kxs
then return k
else fold $ (k,x) : kxs
Nothing -> ioError $ userError $
"Thing:\n" ++ show x ++ "\nat " ++
show (getPos x) ++ "\nhas kind " ++ show k

32

++ ",\nthat is incompatible with expected "


++ show k
fold $ zip ks xs

constant ::= uName | integer | string.


constant

26.5

Instance declarations

26.5.1

integer

Showing

A named kind is represented as *. An integral kind is represented


as #. A string kind is represented as $. Tuples are formed with
parentheses and commas. An unknown kind (what a variable must
have) is represented as ?.
instance Show Kind
showsPrec _ k =
KNamed
->
KIntegral ->
KString
->
KTuple ks ->
KUnknown

27

uName

where
case k of
showChar *
showChar #
showChar $
showChar ( . showWithSep "," ks
. showChar )
-> showChar ?

Logic.Constants

string

constantP recognises constants.


constantP :: Parser Constant
constantP =
tagP "uName" @> (\(_,n,p) -> CNamed n p)
<|> integerP
@> (\(i,p)
-> CIntegral i p)
<|> tagP "string" @> (\(_,s,p) -> CString s p)

27.4

Collecting constants

It is required for various purposes to identify all of the distinct


constants that occur in an object. Constants can be collected from
instances of class HasConstants .
class HasConstants a where

Module Logic.Constants implements constants.


getConstants x C adds any constants in x to C.
module ABR.Logic.Constants (
Constant(..), constantP, integerP, HasConstants(..)
) where
import qualified Data.Set as S
import Control.DeepSeq
import ABR.Util.Pos
import ABR.Parser
import ABR.Logic.Kinds

27.1

Maintenance notes
Data types

An Constant is a fixed token which may appear as an argument to


an atom. Constants may be the traditional named kind ( CNamed ),
an integer ( CIntegral ), or a string ( CString ).
data Constant =
CNamed {
cName :: String,
cPos :: Pos
}
| CIntegral {
cInt :: Integer,
cPos :: Pos
}
| CString {
cStr :: String,
cPos :: Pos
}

27.3

hasConstants x returns True iff x contains constants.


hasConstants :: a -> Bool
hasConstants a = not $ S.null $ getConstants a S.empty

27.5

Reviewed 2013-11-24: Removed qualification.

27.2

getConstants :: a -> S.Set Constant -> S.Set Constant

27.5.1

Comparing

instance Eq Constant where


(CNamed n _)
== (CNamed n _)
(CIntegral i _) == (CIntegral i _)
(CString s _)
== (CString s _)
_
== _

=
=
=
=

n == n
i == i
s == s
False

instance Ord Constant where


compare c c = case c of
CNamed n _
-> case c of
CNamed n _
-> compare n n
CIntegral _ _ -> LT
CString _ _
-> LT
CIntegral i _ -> case c of
CNamed _ _
-> GT
CIntegral i _ -> compare i i
CString _ _
-> LT
CString s _ -> case c of
CNamed _ _
-> GT
CIntegral _ _ -> GT
CString s _
-> compare s s

27.5.2

Parsers

Instance declarations

Positions

instance HasPos Constant where


getPos = cPos

integer ::= ["-"] cardinal.


integer
cardinal
-

integerP recognises integers, returning the integer value and the


position it occured at.
integerP :: Parser (Integer, Pos)
integerP =
optional (literalP "symbol" "-")
<&> tagP "cardinal"
@> (\(ms,(_,c,p)) -> case ms of
[]
-> (read c, p)
[(_,_,p)] -> (negate $ read c, p))

April 3, 2015

27.5.3

Showing

instance Show Constant where


showsPrec _ c = case c of
CNamed n _
-> showString n
CIntegral i _ -> shows i
CString s _
-> showString s

27.5.4

Collecting constants

instance HasConstants Constant where


getConstants = S.insert

33

27.5.5

Kind inference

instance HasKind Constant where


kindCheck c = case c of
CNamed _ _
-> return KNamed
CIntegral _ _ -> return KIntegral
CString _ _
-> return KString

28.5

Grounding

To ground is to substitute a variable with a constant.


A Substitution v :->- c replaces a variable v with a constant
c. Substitutions may be composed. s1 :>-> s2 first performs s1
and then s2 . NullSub is the null substitution that does nothing.
data Substitution =

27.5.6

DeepSeq

instance NFData
rnf c = case
CNamed
CIntegral
CString

28

Constant
c of
n p -> n
i p -> i
s p -> s

where
deepseq p deepseq ()
deepseq p deepseq ()
deepseq p deepseq ()

Logic.Variables

Module ABR.Logic.Variables implements variables.


module ABR.Logic.Variables (
Variable(..), variableP, HasVariables(..),
Substitution(..), Groundable(..)
) where
import qualified Data.Set as S
import Control.DeepSeq
import
import
import
import

28.1

NullSub
| Variable :->- Constant
| Substitution :>-> Substitution
deriving (Eq, Ord, Show)

Anything groundable should be an instance of class Groundable .


class Groundable a where
ground1 v c x returns x with all occurrences of variable v replaced
by constant c.
ground1 :: Variable -> Constant -> a -> a
ground s x applies substitution s to x.
ground :: Substitution -> a -> a
ground s x = case s of
NullSub
-> x
v :->- c
-> ground1 v c x
s1 :>-> s2 -> ground s2 $ ground s1 x
rename v v 0 x returns x with all occurrences of variable v replaced
by another variable v 0 .

ABR.Util.Pos
ABR.Parser
ABR.Logic.Kinds
ABR.Logic.Constants

rename :: Variable -> Variable -> a -> a

Maintenance notes

28.6

Instance declarations

Reviewed 2013-11-24: Removed qualification.

28.6.1

28.2

instance Eq Variable where


(Variable n _) == (Variable n _) = n == n

Data type

An Variable is a token which may appear as an argument to an


atom, to be instatiated with constants.
data Variable =
Variable {
vName :: String,
vPos :: Pos
}

28.3

Comparing

instance Ord Variable where


compare (Variable n _) (Variable n _) = compare n n

28.6.2

Positions

instance HasPos Variable where


getPos = vPos

Parsers

28.6.3

Showing

instance Show Variable where


showsPrec _ (Variable n _) = showString n

variable ::= lName.


variable
lName

28.6.4

Collecting variables

variableP recognises variables.


variableP :: Parser Variable
variableP =
tagP "lName"
@> (\(_,n,p) -> Variable n p)

28.4

Collecting variables

It is required for various purposes to identify all of the distinct


variables that occur in an object. Variables can be collected from
instances of class HasVariables .
class HasVariables a where
getVariables x V adds any variables in x to V .

instance HasVariables Variable where


getVariables = S.insert

28.6.5

Grounding

instance Groundable a => Groundable [a] where


ground1 v c = map (ground1 v c)
rename v v = map (rename v v)

28.6.6

Kind inference

instance HasKind Variable where


kindCheck c = case c of
Variable _ _
-> return KUnknown

getVariables :: a -> S.Set Variable -> S.Set Variable


hasVariables x returns True iff x contains variables.
hasVariables :: a -> Bool
hasVariables a = not $ S.null $ getVariables a S.empty

April 3, 2015

28.6.7

DeepSeq

instance NFData Variable where


rnf (Variable v p) = v deepseq p deepseq ()

34

29

Logic.Arguments

29.4

Module ABR.Logic.Arguments implements arguments for logic systems.


module ABR.Logic.Arguments (
Argument(..), argumentP
) where

29.1

instance
Const
Var
Tuple

ABR.Util.Pos
ABR.Parser
ABR.Text.Showing
ABR.Logic.Kinds
ABR.Logic.Constants
ABR.Logic.Variables

Comparing
Eq
c
v
as

Argument where
_ == Const c _ = c == c
_ == Var
v _ = v == v
_ == Tuple as _ = as == as

instance Ord Argument where


compare a1 a2 = case a1 of
Const c _ -> case a2 of
Const c _ -> compare c c
Var
_ _ -> LT
Tuple _ _ -> LT
Var
v _ -> case a2 of
Const _ _ -> GT
Var
v _ -> compare v v
Tuple _ _ -> LT
Tuple as _ -> case a2 of
Const _
_ -> GT
Var
_
_ -> GT
Tuple as _ -> compare as as

Maintenance notes

Reviewed 2013-11-24: Removed qualification.

29.2

Positions

instance HasPos Argument where


getPos = arPos

29.4.2

import Control.DeepSeq
import
import
import
import
import
import

29.4.1

Instance declarations

Data type

An Argument of an atom may be either:


a constant ( Const );
a variable ( Var ); or

29.4.3

Showing

a tuple containing constants and/or variables.


instance Show Argument where
showsPrec _ a = case a of
Const c _ -> shows c
Var
v _ -> shows v
Tuple as _ -> showChar ( . showWithSep "," as .
showChar )

data Argument =
Const {
arConst :: Constant,
arPos
:: Pos
}
| Var {
arVar :: Variable,
arPos :: Pos
}
| Tuple {
arElems :: [Argument],
arPos
:: Pos
}

29.4.4

instance HasConstants Argument where


getConstants a cs = case a of
Const c _ -> getConstants c cs
Var
_ _ -> cs
Tuple as _ -> foldr getConstants cs as

The order of a tuples elements must be maintained. A tuple has at


least 2 elements. A nested tuple is equivalent to a flat one.

Parsers

argument ::=
constant
| variable
| "(" argument {"," argument}+ ")".

29.4.6

argument
constant
variable
(

argument

argument

argumentP recognises arguments.


argumentP :: Parser Argument
argumentP = (
constantP @> (\c -> Const c (getPos c))
<|> variableP @> (\v -> Var v (getPos v))
<|>
literalP "symbol" "("
<&> nofail "argument expected" argumentP
<&> some (
literalP "symbol" ","
&> nofail "argument expected" argumentP
)
<& nofail (literalP "symbol" ")")
@> (\((_,_,p),(a,as)) -> Tuple (a:as) p)
)

April 3, 2015

29.4.5

Collecting variables

instance HasVariables Argument where


getVariables a vs = case a of
Const _ _ -> vs
Var
v _ -> getVariables v vs
Tuple as _ -> foldr getVariables vs as

((x, y), z) = (x, y, x) = (x, (y, z))

29.3

Collecting constants

Grounding

instance Groundable Argument where


ground1 v c a = case a of
Const c p
-> Const c
Var
v p | v == v
-> Const c
| otherwise -> Var
v
Tuple as p
->
Tuple (map (ground1 v c) as) p
rename v v a = case a of
Const c
p
-> Const c
Var
v p | v == v -> Var v
| otherwise -> Var v
Tuple as p
->
Tuple (map (rename v v) as) p

29.4.7

p
p
p

p
p
p

Kind inference

instance HasKind Argument where


kindCheck a = case a of
Const c _ -> kindCheck c
Var
v _ -> kindCheck v
Tuple as _ -> do
ks <- mapM kindCheck as
return $ KTuple ks

35

29.4.8

DeepSeq
specialAtom ::= argument ("<" | "<=" | "==") argument.

instance NFData Argument where


rnf a = case a of
Const c p -> c deepseq p deepseq ()
Var
v p -> v deepseq p deepseq ()
Tuple as p -> as deepseq p deepseq ()

specialAtom
argument

<

argument

<=
==

30

Logic.Atoms

atom ::= atomName [argList] | string | specialAtom.

Module ABR.Logic.Atoms implements atoms.

atom
atomName

module ABR.Logic.Atoms (
Atom(..), atomNameP, atomP, HasAtoms(..)
) where

argList
string

import qualified Data.Set as S


import Control.DeepSeq
import
import
import
import
import
import
import

30.1

specialAtom

atomP recognises atoms.

ABR.Util.Pos
ABR.Parser
ABR.Text.Showing
ABR.Logic.Kinds
ABR.Logic.Constants
ABR.Logic.Variables
ABR.Logic.Arguments

Maintenance notes

Reviewed 2013-11-24: Removed qualification.

30.2

Data type

An Atom is a proposition symbol, Prop . An atom may have a list


of arguments.
data Atom =
Prop {
aName :: String,
aArgs :: [Argument],
aPos :: Pos
}

30.3

atomP :: Parser Atom


atomP =
argumentP
<&> (
literalP "symbol" "<"
<|> literalP "symbol" "<="
<|> literalP "symbol" "=="
)
<&> nofail argumentP
@> (\(a1,((_,op,_),a2)) ->
Prop op [a1,a2] (getPos a1))
<|>
atomNameP
<&> optional argListP
@> (\((n,p),ass) -> case ass of
[]
-> Prop n [] p
[as] -> Prop n as p
)
<|> tagP "string"
@> (\(_,s,p) -> Prop s [] p)

30.4

Collecting atoms

It is required for various purposes to identify all of the distinct atoms


that occur in an object. Atoms can be collected from instances of
class HasAtoms .

Parsers

class HasAtoms a where


atomName ::= lName | uName.

getAtoms x A adds any atoms in x to A.

atomName

getAtoms :: a -> S.Set Atom -> S.Set Atom

lName

30.5

uName

atomNameP recognises atom names.

30.5.1

atomNameP :: Parser (String, Pos)


atomNameP =
(
tagP "lName"
<|> tagP "uName"
)
@> (\(_,n,p) -> (n, p))

instance Ord Atom where


compare (Prop n as _) (Prop n as _) =
case compare n n of
LT -> LT
EQ -> compare as as
GT -> GT

argList
argument

)
,

argument

.
argListP recognises argument lists.
argListP :: Parser [Argument]
argListP =
literalP "symbol" "("
&> argumentP
<&> many (literalP "symbol" "," &> argumentP)
<& nofail (literalP "symbol" ")")
@> cons
.

April 3, 2015

Comparing

instance Eq Atom where


(Prop n as _) == (Prop n as _) = n == n && as == as

argList ::= "(" argument {"," argument} ")".

Instance declarations

30.5.2

Positions

instance HasPos Atom where


getPos = aPos

30.5.3

Showing

instance Show Atom where


showsPrec _ (Prop "<" [a1,a2] _) =
shows a1 . showString " < " . shows a2
showsPrec _ (Prop "<=" [a1,a2] _) =
shows a1 . showString " <= " . shows a2
showsPrec _ (Prop "==" [a1,a2] _) =
shows a1 . showString " == " . shows a2
showsPrec _ (Prop n as _) = case as of

36

literal

[] -> showString n
as -> showString n . showChar ( .
showWithSep "," as . showChar )

30.5.4

atom

Collecting Atoms

instance HasAtoms Atom where


getAtoms = S.insert

literal

literal

pLiteralP recognises literals.

instance HasVariables Atom where


getVariables (Prop _ as _) vs = foldr getVariables vs as

pLiteralP :: Parser Literal


pLiteralP =
atomP
@> (\a -> Pos a (getPos a))
<|>
literalP "symbol" "~"
<&> pLiteralP
@> (\((_,_,p),l) -> neg (l {lPos = p}))
<|>
literalP "symbol" "("
<&> pLiteralP
<& literalP "symbol" ")"
@> (\((_,_,p),l) -> l {lPos = p})

30.5.7

31.4

30.5.5

Collecting constants

instance HasConstants Atom where


getConstants (Prop _ as _) cs = foldr getConstants cs as

30.5.6

Collecting variables

Grounding

instance Groundable Atom where


ground1 v c (Prop n as p) =
Prop n (map (ground1 v c) as) p
rename v v (Prop n as p) =
Prop n (map (rename v v) as) p

30.5.8

DeepSeq

instance NFData Atom where


rnf (Prop n as p) =
n deepseq as deepseq p deepseq ()

31

Logic.Literals

Negation

Class Negatable includes types that may be logically negated with


.
class Negatable a where
neg x negates x. For example if a is an atom, neg a = a, and
neg a = a.
neg :: a -> a
pos x returns the positive x. For example if a is an atom, pos a =
pos a = a.
pos :: a -> a

Module ABR.Logic.Literals implements literals.

Class Complementable includes types that may be logically complemented with .

{-# LANGUAGE FlexibleInstances #-}

class Complementable a where

module ABR.Logic.Literals (
Literal(..), pLiteralP, Negatable(..),
Complementable(..)
) where

comp x complements x. For example if a is an atom, comp a =


a = a.
comp :: a -> a

import Control.DeepSeq
import
import
import
import
import

31.1

ABR.Util.Pos
ABR.Parser
ABR.Logic.Constants
ABR.Logic.Variables
ABR.Logic.Atoms

Maintenance notes

Reviewed 2013-11-24: Removed qualification.

31.2

Data type

A Literal is any atom a ( Pos ) or its negation a ( Neg ).


data Literal =
Pos {
lAtom ::
lPos ::
}
| Neg {
lAtom ::
lPos ::
}

31.3

Atom,
Pos

Atom,
Pos

Parser

literal ::= atom | "~" literal | "(" literal ")".

April 3, 2015

31.5
31.5.1

Instance declarations
Comparing

instance Eq Literal where


(Pos a _) == (Pos a _) = a == a
(Neg a _) == (Neg a _) = a == a
_
== _
= False
instance Ord Literal where
compare l l = case l of
Pos a _ -> case l of
Pos a _ -> compare a a
Neg _ _ -> GT
Neg a _ -> case l of
Pos _ _ -> LT
Neg a _ -> compare a a

31.5.2

Positions

instance HasPos Literal where


getPos = lPos

31.5.3

Showing

instance Show Literal where


showsPrec p l = case l of
Pos a _ -> shows a
Neg a _ -> showChar ~ . shows a

37

31.5.4

32.2

Negation

Data types

instance Negatable Literal where


neg l = case l of
Pos a p -> Neg a p
Neg a p -> Pos a p
pos l = case l of
Pos a p -> Pos a p
Neg a p -> Pos a p

A Quine-McCluskey bit ( QMBit ) is either zero ( Zer ), one ( One )

instance Complementable Literal where


comp = neg

This is a label with which to tag a term, and those derived from it.
The original terms will be labelled 0, 1, . . .

or a placholding dash ( Dsh ). A list of them is a bit string. A list of


bit strings is a formula. This modules purpose is the simplification
of such a formula.
data QMBit = Zer | Dsh | One
deriving (Eq, Show)

type QMLbl = Int

31.5.5

Collecting Atoms

A QMTree stores a collection of bit strings. A bit string is a sequence


of zeroes, ones and dont care dashes. The fork constructor ZDO
starts branches that start with a zero, dash or one respectively. A
leaf constructor N indicates that that branch does not represent a
valid bit string, whereas the leaf constructor Y ends a valid bit string.
The label that is attached to each Y idicates which of the original
terms this bit string either is or covers. The label is a list of ints in
strictly ascending order.

instance HasAtoms Literal where


getAtoms l = case l of
Pos a _ -> getAtoms a
Neg a _ -> getAtoms a

31.5.6

Collecting constants

instance HasConstants Literal


getConstants l cs = case l
Pos a _ -> getConstants
Neg a _ -> getConstants

31.5.7

where
of
a vs
a vs

Grounding

instance Groundable Literal where


ground1 v c l = case l of
Pos a p -> Pos (ground1 v c
Neg a p -> Neg (ground1 v c
rename v v l = case l of
Pos a p -> Pos (rename v v
Neg a p -> Neg (rename v v

31.5.9

a) p
a) p
a) p
a) p

DeepSeq

instance NFData Literal where


rnf l = case l of
Pos a p -> a deepseq p deepseq ()
Neg a p -> a deepseq p deepseq ()

32

Logic.QuineMcClusky

ABR.Logic.QuineMcClusky
implements the QuineModule
McClusky algorithm for simplifying boolean expressions as
described in Rosen[3].
module ABR.Logic.QuineMcClusky (
QMBit(..), qmSimplify
) where
import
import
import
import
import
import
import

Control.DeepSeq
Data.List
Data.Maybe
Data.Array.IArray
Data.Array.MArray
Data.Array.IO
System.IO

import ABR.Data.List

32.1

Maintenance notes

Reviewed 2013-11-24. removed dependence on deprecated modules.


Reviewed 2012-11-24: moved into ABR.Logic. It also needs to really
be completed, and made to use standard data types.

April 3, 2015

data QMTree =

ZDO QMTree QMTree QMTree


| Y [QMLbl]
| N
deriving (Show)

Example QMTrees:

Collecting variables

instance HasVariables Literal


getVariables l vs = case l
Pos a _ -> getVariables
Neg a _ -> getVariables

31.5.8

where
of
a cs
a cs

0:
0:
1:
0:
1:
2:

[]
[0]
[1]
[00]
[-0]
[10]

N
Y [0]
ZDO (Y [0]) N (Y [1])

ZDO
(ZDO (Y [0])
(ZDO (Y [1])
(ZDO (Y [2])
0: [00]
ZDO
1: [10]
(ZDO (Y [0])
N
(ZDO (Y [1])
A term is a list of bits, tagged by the list
original terms from which it is derived.

N N)
N N)
N N)
N N)
N N)
of labels assigned to the

type QMTerm = ([QMLbl], [QMBit])

32.3

Conversion from lists to trees

entree bss l returns (t, l0 ) where t is the tree that is equivalent to


the list of bit strings bss. l is the first label to assign to one of bss
and l0 is the next available label.
entree :: [[QMBit]] -> QMLbl -> (QMTree, QMLbl)
entree bss n = case bss of
[]
-> (N, n)
[[]] -> (Y [n], n + 1)
_
-> let (zs,ds,os) = partitionBs bss [] [] []
(zs,n) = entree zs n
(ds,n) = entree ds n
(os,n) = entree os n
in (ZDO zs ds os, n)
partitionBs bss [] [] [] returns (zs, ds, os) where zs are the tails
of the strings in bss that started with zeroes, zs are the tails of the
strings in bss that started dashes, and zs are the tails of the strings
in bss that started with ones.
partitionBs ::
[[QMBit]] -> [[QMBit]] -> [[QMBit]] -> [[QMBit]] ->
([[QMBit]], [[QMBit]], [[QMBit]])
partitionBs bss zs ds os = case bss of
[]
-> (zs, ds, os)
bs:bss -> case bs of
Zer:bs -> partitionBs bss (bs:zs) ds os
Dsh:bs -> partitionBs bss zs (bs:ds) os
One:bs -> partitionBs bss zs ds (bs:os)

32.4

Conversion from trees to lists

detree t converts a tree to a list of terms (labels and bit strings).

38

detree :: QMTree -> [QMTerm]


detree t = case t of
N
-> []
Y l
-> [(l,[])]
ZDO z d o ->
map (\(l,bs) -> (l, Zer : bs)) (detree z)
++ map (\(l,bs) -> (l, Dsh : bs)) (detree d)
++ map (\(l,bs) -> (l, One : bs)) (detree o)

32.5

Combining QMTrees

tCombine t t0 returns the bit strings that result from successful combinations of the bit strings in t and t0 . All the bit strings in t and
t0 have the same number of 1s and that number for t differs from
that of t0 by 1.
tCombine :: QMTree -> QMTree -> QMTree
tCombine t t = case t of
ZDO z d o -> case t of
ZDO z d o -> ZDO
(tCombine z z)
(tUnion (tCombine d d)
(tUnion (tSect z o) (tSect o z)))
(tCombine o o)
_
-> N
_
-> N
tSect t t0 returns the bit strings that occur in t and t0 .
tSect :: QMTree -> QMTree -> QMTree
tSect t t = case t of
N
-> N
Y l
-> case t of
Y l
-> Y (mnub l l)
N
-> N
ZDO _ _ _ -> error
"ABR.QuineMcClusky.tSect: mismatched length"
ZDO z d o -> case t of
ZDO z d o -> case (tSect z z,
tSect d d,
tSect o o) of
(N,
N,
N)
-> N
(z, d, o) -> ZDO z d o
_
-> N
tUnion t t0 returns the bit strings that occur in either t or t0 .
tUnion :: QMTree -> QMTree -> QMTree
tUnion t t = case t of
N
-> t
Y l
-> case t of
N
-> t
Y l
-> Y (mnub l l)
ZDO _ _ _ -> error
"ABR.QuineMcClusky.tUnion: mismatched length"
ZDO z d o -> case t of
N
-> t
ZDO z d o -> ZDO (tUnion z z)
(tUnion d d)
(tUnion o o)
Y _
-> error
"ABR.QuineMcClusky.tUnion: mismatched length"
diff t t0 returns the bit strings that occur in t but not in t0 .
diff :: QMTree -> QMTree -> QMTree
diff t t = case t of
N
-> N
Y _
-> case t of
N
-> t
Y _
-> N
ZDO _ _ _ -> error
"ABR.QuineMcClusky.diff: mismatched length"
ZDO z d o -> case t of
N
-> t
ZDO z d o -> case (diff z z,
diff d d,
diff o o) of
(N,
N,
N)
-> N
(z, d, o) -> ZDO z d o
Y _
-> error
"ABR.QuineMcClusky.tUnion: mismatched length"

April 3, 2015

unusedOne t t0 returns the bit strings that occur in t but not used
in t0 . If it were used in t0 then there would be a bit string in t0 that
was exactly the same but for one one changed to a dash.
unusedOne :: QMTree -> QMTree -> QMTree
unusedOne t t = case t of
N
-> N
Y _
-> t
ZDO z d o -> case t of
N
-> t
ZDO z d o -> case (unusedOne z z,
unusedOne d d,
diff (unusedOne o o)
(tSect o d)) of
(N,
N,
N)
-> N
(z, d, o) -> ZDO z d o
Y _
-> error
"ABR.QuineMcClusky.unusedOne: mismatched length"
unusedZero t t0 returns the bit strings that occur in t but not used
in t0 . If it were used in t0 then there would be a bit string in t0 that
was exactly the same but for one zero changed to a dash.
unusedZero :: QMTree -> QMTree -> QMTree
unusedZero t t = case t of
N
-> N
Y _
-> t
ZDO z d o -> case t of
N
-> t
ZDO z d o -> case (diff (unusedZero z z)
(tSect z d),
unusedZero d d,
unusedZero o o) of
(N,
N,
N)
-> N
(z, d, o) -> ZDO z d o
Y _
-> error
"ABR.QuineMcClusky.unusedZero: mismatched length"

32.6

Simplification

countOnes bs returns the number of ones in bs.


countOnes :: [QMBit] -> Int
countOnes bs = sum [1 | One <- bs]
groupByOnes bss takes the original set of bit strings and groups them
by how many ones they contain.
groupByOnes :: [[QMBit]] -> IO (Array Int [[QMBit]])
groupByOnes bss = do
let n = length $ head bss
a :: Array Int [[QMBit]]
a = accumArray (flip (:)) [] (0,n)
[(countOnes bs, bs) | bs <- bss]
return a
makeTrees a takes the array of sets of bit strings, grouped by ones,
and creates a mutable array of trees. In doing so it assigns labels.
makeTrees :: Array Int [[QMBit]] -> IO (IOArray Int QMTree)
makeTrees a = do
let (0,n) = bounds a
a <- newArray (0,n) N
let entrees :: Int -> QMLbl -> IO QMLbl
entrees i l
| i <= n
= do
let (t,l) = entree (a!i) l
writeArray a i t
entrees (i+1) l
| otherwise = return l
entrees 0 1
return a
primeImplicants a takes an array of trees grouped by numbers of
ones in each a, and returns all those terms that could not be reduced
by combination.
primeImplicants :: IOArray Int QMTree -> IO [QMTerm]
primeImplicants a = do
(0,n) <- getBounds a
unusedOnes <- mapArray id a
irrs <- pi1 a n 0 unusedOnes
return irrs

39

pi1 a n i unusedOnes is the outer loop. i ranges from 0 to n. At


i = n, there are no more combinations possible, so just return the
single array element left as the irreducible terms. For 0 < i < n,
We call fi2 to do a sweep that combines elements i + 1 down to 0.
pi1 :: IOArray Int QMTree -> Int -> Int
-> IOArray Int QMTree -> IO [QMTerm]
pi1 a n i unusedOnes
| n == 0
= do
t <- readArray a 0
return $ detree t
| i == n
= do
us <- readArray unusedOnes 1
bs <- readArray a 1
let irrs = detree $ tSect us bs
irrs <- readArray a 0
return $ irrs ++ detree irrs
| otherwise = do
irrs <- pi2 a i unusedOnes
irrs <- pi1 a n (i+1) unusedOnes
return $ irrs ++ irrs
pi2 a j performs a sweep of combinations of elements from j + 1
down to 0.
pi2 :: IOArray Int QMTree -> Int -> IOArray Int QMTree
-> IO [QMTerm]
pi2 a j unusedOnes
| j < 0
= return []
| otherwise = do
t <- readArray a (j+1)
t <- readArray a j
uos <- readArray unusedOnes j
let t = tCombine t t
uos = unusedOne t t
uzs = unusedZero t t
irrs = detree $ tSect uos uzs
writeArray a j t
writeArray unusedOnes (j+1) uos
writeArray unusedOnes j t
irrs <- pi2 a (j-1) unusedOnes
return $ irrs ++ irrs
findMinimalCoverage uncovered coveredBy nepiis pia finds the
smallest selection from prime implicants (stored in pia) that are
not essential prime implicants (with numbers in nepiis) and cover
the original terms not covered by essential prime implicants (their
labels are in uncovered). coveredBy maps original term labels to
the numbers of the prime implicants that cover them.
findMinimalCoverage :: [QMLbl] -> Array QMLbl [Int]
-> [Int] -> Array Int QMTerm -> IO [Int]
findMinimalCoverage uncovered coveredBy nepiis pia =
fmc uncovered nepiis []
where
fmc :: [QMLbl] -> [Int] -> [Int] -> IO [Int]
fmc [] nepiis part =
return part
fmc (i:is) nepiis part = do
let pis = osect (reverse (coveredBy ! i)) nepiis
j = head pis
ls = fst (pia ! j)
is = odiff is ls
nepiis = delete j nepiis
part = j : part
fmc is nepiis part
qmSimplify bss simplifies bss.
qmSimplify :: [[QMBit]] -> IO [[QMBit]]
qmSimplify bss = do
-- group bit strings by # 1s
a <- groupByOnes bss
-- turn them into trees and assign labels
ts <- makeTrees a
-- find the PIs
pis <- primeImplicants ts
let -- # original terms
n_bss = length bss
-- # PIs
n_pis = length pis
-- save PIs in an array with unique index with

April 3, 2015

-- PIs that cover the most original terms having


-- lower indices
pia :: Array Int QMTerm
pia = array (1,n_pis) $ zip [1..] $ sortBy
(\(ls,_) (ls,_) ->
compare (length ls) (length ls)) pis
-- each original term covered by what PIs?
coveredBy :: Array QMLbl [Int]
coveredBy = accumArray (flip (:)) [] (1,n_bss) $
[(l,i) | i <- [1..n_pis], l <- fst (pia ! i)]
-- which PIs are EPIs
epiis = snub [j
| i <- [1..n_bss], [j] <- [coveredBy ! i]]
-- the EPI bits strings and labels
(epils,epibs) = unzip [pia ! i | i <- epiis]
-- all originals covered by EPIs
covered = foldl mnub [] epils
-- all originals not covered by EPIs
uncovered = odiff [1..n_bss] covered
-- which PIs are not EPIs
nepiis = odiff [1..n_pis] epiis
nepiis <- findMinimalCoverage uncovered coveredBy
nepiis pia
let -- the not essential PI bits strings needed
nepibs = [snd (pia ! i) | i <- nepiis]
return $ epibs ++ nepibs

32.7
32.7.1

Instance declarations
DeepSeq

instance NFData QMBit where { }

33

Graphics.Geometry

Module ABR.Graphics.Geometry implements some basic geometric


calculations.
{-# LANGUAGE TypeSynonymInstances,
FlexibleInstances #-}
module ABR.Graphics.Geometry (
Point, Box, Angle,
GeoNum(
netBox, shiftBoxes, leastRightShift,
placeAroundOval, iGeo, iPoint, iBox,
insetBox
)
) where
import Data.List

33.1

Maintenance notes

Reviewed 2014-06-03: Made all the -Walls go away.


Reviewed 2013-11-24.
Reviewed 2012-11-24: Moved into ABR.Graphics.

33.2

Data types

A Point on the plane in Cartesian coordinates (x, y). It is assumed


that the coordinate system in conventional, with the y-axis the right
way up, unlike most screen graphics coordinate systems. The actual
numeric type is not specified, and where possible functions will be
written to accomodate both any of Float, Double, Int, or Integer
or Rational. See class GeoNum, below.
type Point a = (a, a)
A Box (l, b, r, t) is a rectangle defined by its left l, bottom b, right
r and top t. It is assumed that l r and b t.
type Box a = (a, a, a, a)
Angle s are represented in degrees. Absolute angles are measured
anticlockwise from the positive x-axis.
type Angle a = a

40

(x,y)

Line s are represented by the coefficients of the general formula for


a line (A, B, C) in:

b
a

Ax + by + C = 0

type Line a = (a, a, a)


LineSeg ments are represented by the start and end points.
type LineSeg a = (Point a, Point a)

33.3

Geometric computations

GeoNum overloads functions which perform geometric computations.


class (Ord a, Num a) => GeoNum a where
netBox boxes returns the smallest box that encloses all of boxes.
netBox :: [Box a] -> Box a
netBox boxes =
let (ls, bs, rs, ts) = unzip4 boxes
in (minimum ls, minimum bs, maximum rs, maximum ts)
shiftBoxes boxes x y returns the boxes displaced by x and
y .
shiftBoxes :: [Box a] -> a -> a -> [Box a]
shiftBoxes bs dx dy = [(l + dx, b + dy, r + dx, t + dy)
| (l,b,r,t) <- bs]
leastRightShift as bs returns the least horizontal displacement
so that list of boxes bs no longer overlap list of boxes as, as in
figure 1.

Figure 2: An ellipse.
iBox b converts the coordinates of box b to Ints.
iBox :: Box a -> Box Int
iBox (l,b,r,t) = (iGeo l, iGeo b, iGeo r, iGeo t)
insetBox d (l, b, r, t) reduces box (l, b, r, t) all around by distance
d. It is assumed that 2d r l and 2d t b, that is that the
original box was big enough to do this.
insetBox :: a -> Box a -> Box a
insetBox d (l,b,r,t) = (l + d, b + d, r - d, t - d)
segToLine ((x1 , y1 ), (x2 , y2 )) computes (A, B, C) for segment
((x1 , y1 ), (x2 , y2 )). Working:
A = y2 y1 , B = x2 x1 , C = (Ax1 + By1 )
segToLine :: LineSeg a -> Line a
segToLine ((x1,y1),(x2,y2)) =
let a = y2 - y1
b = x1 - x2
in (a, b, -(a * x1 + b * y1))
insetSeg d ((x1 , y1 ), (x2 , y2 )) clips a distance d from both ends of
the line segment. It is assumed that the line segment is long enough
to do this.
insetSeg :: a -> LineSeg a -> LineSeg a
segToLine d ((x1,y1),(x2,y2)) = undefined

33.4

Instance declarations

instance GeoNum Float where


iGeo = round
instance GeoNum Double where
iGeo = round

Figure 1: The least right shift to stop two sets of boxes


overlapping.
leastRightShift :: [Box a] -> [Box a] -> a
leastRightShift as bs = case [r - l |
(_, b, r, t) <- as, (l, b, _, t) <- bs,
t >= b && b <= t] of
[] -> 0
xs -> maximum xs
distAroundOval n o returns a list of n points distributed around
the oval inscribed in box o. The points will be equally distributed
by angle of separation wrt to the centre of the oval, clockwise, and
starting from angle .
placeAroundOval :: Int -> Box a -> Angle a -> [Point a]
placeAroundOval = undefined
Working: An ellipse is defined by either:
x2
y2
+ 2 =1
a2
b
or the parametric equations:
x = a cos t and y = b sin t
iGeo x converts a GeoNum x to an Int.
iGeo :: a -> Int
iPoint p converts the coordinates of point p to Ints.
iPoint :: Point a -> Point Int
iPoint (x,y) = (iGeo x, iGeo y)

April 3, 2015

placeAroundOval n (l,b,r,t) p =
let p = p * pi / 180.0
g = 2.0 * pi / fromIntegral n
a = (r - l) / 2.0
b = (t - b) / 2.0
in [(l + a + a * cos t, b + b + b * sin t)
| i <- [0..n-1], let t = p - fromIntegral i * g]
instance GeoNum Int where
iGeo = id
instance GeoNum Integer where
iGeo = fromIntegral
instance GeoNum Rational where
iGeo = round

34

Graphics.EPS

Module ABR.Graphics.EPS provides support for the composition


of Encapsulated PostScript (EPS).
module ABR.Graphics.EPS (
EPS, PS, BPS, bpsToEps, EPSDrawable(..),
joinBPS, setUpFonts,
times10Width, times10ItalWidth, helvetica10Width,
helvetica10ObliqueWidth, helvetica10BoldWidth,
helvetica10BoldObliqueWidth, symbol10Width,
FontTag(..), FontString(..), ftWidth, fsWidth,
(++-++), MakeFontTags(..), FontBlock(..),
wrapWithinWidth, psStr, newpath, moveto, lineto,
closepath, stroke, show_, arc, gsave, grestore,
translate, box
) where

41

import ABR.Util.Args
import ABR.Graphics.Geometry

34.1

Maintenance notes

Reviewed
Reviewed
Reviewed
Reviewed

2014-06-06: Made all the -Walls go away.


2013-11-24.
2012-11-24: Moved into ABR.Graphics.
2012-05-23: Added underlining and more fonts.

34.2

Data types

EPS is plain text, consisting of some header comments followed by


drawing commands in PostScript. The header comments are very
important as they identify the text as EPS and specify a bounding box in which the figure appears. Any drawing outside of the
bounding box is clipped.

setUpFonts :: PS
setUpFonts = [
"/symbol10 { fs10 setfont } def",
"/times10 { ftr10 setfont } def",
"/times10ital { fti10 setfont } def",
"/helv10 { fh10 setfont } def",
"/helv10obl { fho10 setfont } def",
"/helv10bld { fhb10 setfont } def",
"/helv10bldobl { fhbo10 setfont } def",
"/ftr10 /Times-Roman findfont 10 scalefont def",
"/fti10 /Times-Italic findfont 10 scalefont def",
"/fh10 /Helvetica findfont 10 scalefont def",
"/fho10 /Helvetica-Oblique findfont 10 scalefont def",
"/fhb10 /Helvetica-Bold findfont 10 scalefont def",
"/fhbo10 /Helvetica-BoldOblique findfont 10 scalefont\
\ def",
"/fs10 /Symbol findfont 10 scalefont def"
]

type EPS = String

34.6.2

A PS is a sequence lines of PostScript code in reverse order. We


build up a figure in reverse order initially to avoid a lot of use of ++.

times10Width c returns the width of a character c in Times-Roman


10 point, in 72 dpi pixels. (not exhaustive)

type PS = [String]

times10Width
times10Width

->
!
->
"
->
#
->
$
->
%
->
&
->
\
->
(
->
)
->
*
->
+
->
,
->
-
->
.
->
/
->
0
->
1
->
2
->
3
->
4
->
5
->
6
->
7
->
8
->
9
->
:
->
;
->
<
->
=
->
>
->
?
->
@
->
A
->
B
->
C
->
D
->
E
->
F
->
G
->
H
->
I
->
J
->
K
->
L
->
M
->
N
->
O
->
P
->
Q
->
R
->
S
->
T
->

A BPS is a figure in construction with its PS code and a list of


bounding boxes that enclose the elements of the figure.
type BPS = ([Box Double], PS)

34.3

Finalizing to EPS

bpsToEps b finalizes a BPS figure by reversing it and constructing


the EPS header comment including the bounding box.
bpsToEps :: BPS -> EPS
bpsToEps (bs, css) =
let (l,b,r,t) = netBox bs
in unlines $
["%!PS-Adobe-2.0 EPSF-1.2",
"%%BoundingBox: " ++ unwords (map show
[floor (l - 1) :: Int, floor (b - 1),
ceiling (r + 1), ceiling (t + 1)]),
"%%EndComments"
] ++ reverse css ++ ["showpage"]

34.4

Drawing in BPS

epsDraw options x renders x as a BPS, where x has a data type


which is an instance of EPSDrawable and options contains settings
that might affect the rendering.
class EPSDrawable a where
epsDraw :: Options -> a -> BPS

34.5

Merging BPS components

joinBPS a b x y puts figure b over figure a displaced by x


and y .
joinBPS :: BPS -> BPS -> Double -> Double -> BPS
joinBPS (bs,ps) (bs,ps) dx dy =
let bs = [(l + dx, b + dy, r + dx, t + dy) |
(l,b,r,t) <- bs]
in (bs ++ bs, [grestore] ++ ps ++ [
unwords [gsave, show dx, show dy, translate]
] ++ ps)

34.6
34.6.1

Drawing text
Switching fonts efficiently

setUpFonts is PS code to find the fonts and define procedures for


switching to them.

April 3, 2015

Font metrics

:: Char -> Double


c = case c of
2.5
3.32969
4.07969
5.0
5.0
8.32969
7.77969
3.32969
3.32969
3.32969
5.0
5.63984
2.5
3.32969
2.5
2.77969
5.0
5.0
5.0
5.0
5.0
5.0
5.0
5.0
5.0
5.0
2.77969
2.77969
5.63984
5.63984
5.63984
4.43984
9.20977
7.21992
6.66992
6.66992
7.21992
6.10977
5.55977
7.21992
7.21992
3.32969
3.88984
7.21992
6.10977
8.88984
7.21992
7.21992
5.55977
7.21992
6.66992
5.55977
6.10977

42

U
V
W
X
Y
Z
[
\\
]
^
_

a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
\170
\177
\186
_

->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->

7.21992
7.21992
9.43984
7.21992
7.21992
6.10977
3.32969
2.77969
3.32969
4.68984
5.0
3.32969
4.43894
5.0
4.43894
5.0
4.43894
3.32969
5.0
5.0
2.77969
2.77969
5.0
2.77969
7.77969
5.0
5.0
5.0
5.0
3.32969
3.88984
2.77969
5.0
5.0
7.21992
5.0
5.0
4.43894
4.8
2.0
4.8
5.40977
4.43984 -- open double quote
5.0 -- dash/minus
4.43984 -- close double quote
2.5

times10ItalWidth c returns the width of a character c in TimesItalic 10 point, in 72 dpi pixels. (not exhaustive)
times10ItalWidth :: Char -> Double
times10ItalWidth c = case c of
0
-> 5.0
1
-> 5.0
2
-> 5.0
3
-> 5.0
4
-> 5.0
5
-> 5.0
6
-> 5.0
7
-> 5.0
8
-> 5.0
9
-> 5.0
A
-> 6.10999
B
-> 6.10999
C
-> 6.67
D
-> 7.22
E
-> 6.10999
F
-> 6.10999
G
-> 7.22
H
-> 7.22
I
-> 3.32998
J
-> 4.43999
K
-> 6.67
L
-> 5.55999
M
-> 8.32998
N
-> 6.67
O
-> 7.22
P
-> 6.10999
Q
-> 7.22
R
-> 6.10999

April 3, 2015

S
T
U
V
W
X
Y
Z
_
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
_

->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->

5.0
5.55999
7.22
6.10999
8.32998
6.10999
5.55999
5.55999
5.0
5.0
5.0
4.43999
5.0
4.43999
2.77998
5.0
5.0
2.77998
2.77998
4.43999
2.77998
7.22
5.0
5.0
5.0
5.0
3.88999
3.88999
2.77998
5.0
4.43999
6.67
4.43999
4.43999
3.88999
2.5

helvetica10Width c returns the width of a character c in Helvetica


10 point, in 72 dpi pixels. (not exhaustive)
helvetica10Width :: Char -> Double
helvetica10Width c = case c of

-> 2.77832
!
-> 2.77832
"
-> 3.54981
#
-> 5.56152
$
-> 5.56152
%
-> 8.8916
&
-> 6.66992
\
-> 2.22168
(
-> 3.33008
)
-> 3.33008
*
-> 3.8916
+
-> 5.83984
,
-> 2.77832
-
-> 3.33008
.
-> 2.77832
/
-> 2.77832
0
-> 5.56152
1
-> 5.56152
2
-> 5.56152
3
-> 5.56152
4
-> 5.56152
5
-> 5.56152
6
-> 5.56152
7
-> 5.56152
8
-> 5.56152
9
-> 5.56152
:
-> 2.77832
;
-> 2.77832
<
-> 5.83984
=
-> 5.83984
>
-> 5.83984
?
-> 5.56152
@
-> 10.1514
A
-> 6.66992
B
-> 6.66992
C
-> 7.22168
D
-> 7.22168
E
-> 6.66992

43

F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
[
\\
]
^
_

a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
\170
\177
\186
_

->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->

6.1084
7.77832
7.22168
2.77832
5.0
6.66992
5.56152
8.33008
7.22168
7.77832
6.66992
7.77832
7.22168
6.66992
6.1084
7.22168
6.66992
9.43848
6.66992
6.66992
6.1084
2.77832
2.77832
2.77832
4.69238
5.56152
2.22168
5.56152
5.56152
5.0
5.56152
5.56152
2.77832
5.56152
5.56152
2.22168
2.22168
5.0
2.22168
8.33008
5.56152
5.56152
5.56152
5.56152
3.33008
5.0
2.77832
5.56152
5.0
7.22168
5.0
5.0
5.0
3.33984
2.59766
3.33984
5.83984
3.33008 -- open double quote
5.56152 -- dash/minus
3.33008 -- close double quote
6.33789

helvetica10ObliqueWidth c returns the width of a character c in


Helvetica 10 point oblique, in 72 dpi pixels. (not exhaustive)
helvetica10ObliqueWidth :: Char -> Double
helvetica10ObliqueWidth c = case c of
_
-> helvetica10Width c -- all the same!
helvetica10BoldWidth c returns the width of a character c in Helvetica 10 point bold, in 72 dpi pixels. (not exhaustive)
helvetica10BoldWidth :: Char -> Double
helvetica10BoldWidth c = case c of

-> 2.77832
!
-> 3.33008
"
-> 4.74121
#
-> 5.56152
$
-> 5.56152
%
-> 8.8916

April 3, 2015

&
\
(
)
*
+
,
-
.
/
0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?
@
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
[
\\
]
^
_

a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s

->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->
->

7.22168
2.77832
3.33008
3.33008
3.8916
5.83984
2.77832
3.33008
2.77832
2.77832
5.56152
5.56152
5.56152
5.56152
5.56152
5.56152
5.56152
5.56152
5.56152
5.56152
3.33008
3.33008
5.83984
5.83984
5.83984
6.1084
9.75098
7.22168
7.22168
7.22168
7.22168
6.66992
6.1084
7.77832
7.22168
2.77832
5.56152
7.22168
6.1084
8.33008
7.22168
7.77832
6.66992
7.77832
7.22168
6.66992
6.1084
7.22168
6.66992
9.43848
6.66992
6.66992
6.1084
3.33008
2.77832
3.33008
5.83984
5.56152
2.77832
5.56152
6.1084
5.56152
6.1084
5.56152
3.33008
6.1084
6.1084
2.77832
2.77832
5.56152
2.77832
8.8916
6.1084
6.1084
6.1084
6.1084
3.8916
5.56152

44

t
u
v
w
x
y
z
{
|
}
~
\170
\177
\186
_

->
->
->
->
->
->
->
->
->
->
->
->
->
->
->

3.33008
6.1084
5.56152
7.77832
5.56152
5.56152
5.0
3.8916
2.79785
3.8916
5.83984
5.0 -- open double quote
5.56152 -- dash/minus
5.0 -- close double quote
7.22168

helvetica10BoldObliqueWidth c returns the width of a character c


in Helvetica 10 point bold oblique, in 72 dpi pixels. (not exhaustive)
helvetica10BoldObliqueWidth :: Char -> Double
helvetica10BoldObliqueWidth c = case c of
_
-> helvetica10BoldWidth c -- all the same!
symbol10Width c returns the width of a character c in Symbol 10
point, in 72 dpi pixels. (not exhaustive)
symbol10Width :: Char -> Double
symbol10Width c = case c of
!
-> 3.32969
\34 -> 7.12969 -- for all
#
-> 5.0
\36 -> 5.48984 -- there exists
%
-> 8.32969
&
-> 7.77969
\39 -> 4.38984 -- backwards var epsilon
(
-> 3.32969
)
-> 3.32969
*
-> 5.0
+
-> 5.48984
\45 -> 5.48984 -- dash
/
-> 2.77969
0
-> 5.0
1
-> 5.0
2
-> 5.0
3
-> 5.0
4
-> 5.0
5
-> 5.0
6
-> 5.0
7
-> 5.0
8
-> 5.0
9
-> 5.0
:
-> 2.77969
;
-> 2.77969
<
-> 5.48984
=
-> 5.48984
>
-> 5.48984
?
-> 4.43984
\64 -> 9.20977 -- ~ over =
A
-> 7.21992 -- Alpha
B
-> 6.66992 -- Beta
C
-> 7.21992 -- Chi
D
-> 6.11992 -- Delta
E
-> 6.10977 -- Epsilon
F
-> 7.62969 -- Phi
G
-> 6.02969 -- Gamma
H
-> 7.21992 -- Eta
I
-> 3.32969 -- Iota
J
-> 6.30977 -- var theta
K
-> 7.21992 -- Kappa
L
-> 6.85977 -- Lambda
M
-> 8.88984 -- Mu
N
-> 7.21992 -- Nu
O
-> 7.21992 -- Omicron
P
-> 7.67969 -- Pi
Q
-> 7.40977 -- Theta
R
-> 5.55977 -- Rho
S
-> 5.91992 -- Sigma
T
-> 6.10977 -- Tau
U
-> 6.9
-- Upsilon
V
-> 4.38984 -- var zeta

April 3, 2015

W
-> 7.67969
X
-> 6.45
Y
-> 7.95
Z
-> 6.10977
[
-> 3.32969
\92 -> 2.77969
]
-> 3.32969
\94 -> 6.57969
\95 -> 5.0
\96 -> 5.0
a
-> 6.30977
b
-> 5.48984
c
-> 5.48984
d
-> 4.93984
e
-> 4.43894
f
-> 5.20977
g
-> 4.10977
h
-> 6.02969
i
-> 3.28984
j
-> 6.02969
k
-> 5.48984
l
-> 5.48984
m
-> 5.75977
n
-> 5.20977
o
-> 5.48984
p
-> 5.48984
q
-> 5.20977
r
-> 5.48984
s
-> 6.02969
t
-> 4.38984
u
-> 5.75977
v
-> 7.12969
w
-> 6.85977
x
-> 4.92969
y
-> 6.85977
z
-> 4.93984
{
-> 4.8
|
-> 2.0
}
-> 4.8
~
-> 5.48984
\160 -> 7.61992
\163 -> 5.48984
\172 -> 9.86992
\179 -> 5.48984
\180 -> 5.48984
\184 -> 5.48984
\185 -> 5.48984
\216 -> 7.12969
\217 -> 6.02969
\218 -> 6.02969
_
-> 2.5

34.6.3

-----

Omega
Xi
Psi
Zeta

-- therefore
------------------------------

perp
heavy underscore
overscore
alpha
beta
chi
delta
epsilon
phi
gamma
eta
iota
var phi
kappa
lambda
mu
nu
omicron
pi
theta
rho
sigma
tau
upsilon
var pi
omega
xi
psi
zeta

------------

longer tilde
Euro
le
leftarrow
ge
times
div
ne
not
wedge/and
vee/or

Font tags

Type FontTag tags a string ( ftStr ) with either:

Space a space between symbols at which lines may be broken.

Times10 Times font, roman face, 10 point;

Times10Ital Times font, italic face, 10 point;

Helvetica10 Helvetica font, 10 point;

Helvetica10Oblique Helvetica font, oblique face, 10 point;

Helvetica10Bold Helvetica font, bold face, 10 point;

Helvetica10BoldOblique Helvetica font, bold-olbique face,


10 point; or
Symbol10 Symbol font, 10 point.

Each tag may also optionally be underlined ( ftUnder ).


data FontTag =
Space {
ftUnder :: Bool
}
| Times10 {

45

ftUnder :: Bool,
ftStr
:: String

w fss fs []
= FontString (reverse fs) : fss
w fss [] (Space _ : fs) = w fss [] fs
w fss fs (Space _ : fs) =
w (FontString (reverse fs) : fss) [] fs
w fss fs (f:fs)
= w fss (f : fs) fs

}
| Times10Ital {
ftUnder :: Bool,
ftStr
:: String
}
| Helvetica10 {
ftUnder :: Bool,
ftStr
:: String
}
| Helvetica10Oblique {
ftUnder :: Bool,
ftStr
:: String
}
| Helvetica10Bold {
ftUnder :: Bool,
ftStr
:: String
}
| Helvetica10BoldOblique {
ftUnder :: Bool,
ftStr
:: String
}
| Symbol10 {
ftUnder :: Bool,
ftStr
:: String
}
deriving Show

Instances of class MakeFontTags may be encoded as FontStrings.


class MakeFontTags a where
makeFontTags x renders x as a FontString
makeFontTags :: a -> [FontTag]
makeFontTags = makeFontTagsPrec 0
makeFontTagsPrec p x renders x as a FontString, in parentheses
if p is greater that then precedence of object x (as per showsPrec.
makeFontTagsPrec :: Int -> a -> [FontTag]
makeFontTagsPrec _ = makeFontTags

34.6.5

Type FontBlock is a sequence of FontStrings to be drawn as a


block.
data FontBlock = FontBlock [FontString]
deriving Show
wrapWithinWidth w f wraps FontString f at the Spaces it contains to within maximum width w if possible, returning the wrapped
FontBlock.

ftWidth f returns the total width of FontTag f .


ftWidth :: FontTag -> Double
ftWidth f = case f of
Space
_
->
times10Width
Times10
_ cs ->
sum $ map times10Width cs
Times10Ital
_ cs ->
sum $ map times10Width cs
Helvetica10
_ cs ->
sum $ map helvetica10Width cs
Helvetica10Oblique
_ cs ->
sum $ map helvetica10ObliqueWidth cs
Helvetica10Bold
_ cs ->
sum $ map helvetica10BoldWidth cs
Helvetica10BoldOblique _ cs ->
sum $ map helvetica10BoldObliqueWidth cs
Symbol10
_ cs ->
sum $ map symbol10Width cs

wrapWithinWidth :: Double -> FontString -> FontBlock


wrapWithinWidth w =
FontBlock . wrap 0 (FontString []) . fsWords
where
wrap :: Double -> FontString -> [FontString]
-> [FontString]
wrap _ (FontString []) []
= []
wrap _ fs
[]
= [fs]
wrap n fs
(f:fs) =
let n = fsWidth f
n = n + times10Width + n
in if n < 0.01 then
wrap n f fs
else if n <= w then
wrap n (fs ++-++ f) fs
else
fs : wrap n f fs

34.6.6
34.6.4

Font strings

data FontString = FontString [FontTag]


deriving Show
fsWidth f returns the total width of FontString f .
fsWidth :: FontString -> Double
fsWidth (FontString fs) = sum $ map ftWidth fs
f ++-++ f 0 catenates FontStrings f and f 0 , with a space added at
the join. Iff both of the FontTags at the joining ends are underlined,
the joining space will also be underlined.
(++-++) :: FontString -> FontString -> FontString
(FontString ts) ++-++ (FontString ts) =
FontString (ts ++ Space (not (null ts) &&
not (null ts) && ftUnder (last ts) &&
ftUnder (head ts)) : ts)
a

FontString

into

the

Space-separated

fsWords :: FontString -> [FontString]


fsWords (FontString fs) = reverse $ w [] [] fs
where
w :: [FontString] -> [FontTag] -> [FontTag] ->
[FontString]
w fss [] []
= fss

April 3, 2015

Text encoding

psStr cs encodes a string for inclusion in PostScript as a literal.

Type FontString is a sequence of Strings, tagged by the font they


are to be rendering in.

fsWords f groups
FontStrings.

Font blocks

psStr :: String -> String


psStr cs = ( : concatMap f cs ++ ")"
where
f c = case c of
\n -> "\\n"
\r -> "\\r"
\t -> "\\t"
\b -> "\\b"
\f -> "\\f"
\\ -> "\\\\"
( -> "\\("
) -> "\\)"
_
-> [c]

34.7

Conveniences

Some PostScript operators:


closepath ;

stroke ;

newpath ;

show ;

arc ;

moveto ;
gsave ;

lineto ;
grestore ;

translate .
newpath, moveto, lineto, closepath, stroke, show_,
arc, gsave, grestore, translate :: String
newpath
= "newpath"
moveto
= "moveto"
lineto
= "lineto"
closepath = "closepath"

46

stroke
show_
arc
gsave
grestore
translate

=
=
=
=
=
=

"stroke"
"show"
"arc"
"gsave"
"grestore"
"translate"

Draw a box .
box :: Double -> Double -> Double -> Double -> BPS
box l b r t =
let [l, b, r, t] = map show [l, b, r, t]
in ([(l, b, r, t)], [
unwords [newpath, l, b, moveto, l, t, lineto,
r, t, lineto, r, b, lineto, closepath,
stroke]
])

34.8
34.8.1

Instance declarations
EPSDrawable

([(_,b,r,_)],ps) =
epsDraw options (FontBlock fs)
in ([(l, b - 11, max r r, t)],
ps ++ [grestore] ++ ps ++
["gsave 0 -11 translate"])

35

File.Lock

The ABR.File.Lock module provides a facility to lock a file so that


multiple concurrent processes dont destructively interfere.
{-# language ScopedTypeVariables #-}
module ABR.File.Lock (
lockFile, unlockFile, isLockedFile, areAnyLocked,
lockFiles, unlockFiles, lockGuard, blockGuard
) where
import System.Directory
import qualified Control.Exception as E

instance EPSDrawable FontTag where

elephant
epsDraw _ ft =
let w = ftWidth ft
s = psStr (case ft of
Space _ -> " "
_
-> ftStr ft)
f = case ft of
Space
_
-> "times10"
Times10
_ _ -> "times10"
Times10Ital
_ _ -> "times10ital"
Helvetica10
_ _ -> "helv10"
Helvetica10Oblique
_ _ -> "helv10obl"
Helvetica10Bold
_ _ -> "helv10bld"
Helvetica10BoldOblique _ _ -> "helv10bldobl"
Symbol10
_ _ -> "symbol10"
in ([(0, -2, w, 10)], [
"0 0 moveto " ++ f ++ " " ++ s ++ " show"
] ++ if ftUnder ft then [
"0.4 setlinewidth newpath 0 -1.2 moveto " ++
show w ++ " -1.2 lineto stroke"
] else [])
instance EPSDrawable FontString where

elephant
epsDraw options (FontString fs) =
let p :: [FontTag] -> BPS
p fs = case fs of
[]
-> ([(0,0,0,0)],[])
(f : fs) ->
let ([(l,b,r,t)],ps) = epsDraw options f
([(_,_,r,_)],ps) = p fs
in ([(l, b, r + r, t)], ps ++ [grestore] ++
ps ++ [show r ++ " 0 translate"] ++
[gsave])
in p fs
instance EPSDrawable FontBlock where

snail snail
elephant

epsDraw options (FontBlock fs) = case fs of


[]
-> ([(0,0,0,0)],[])
[f]
-> epsDraw options f
f : fs ->
let ([(l,_,r,t)],ps) = epsDraw options f

April 3, 2015

35.1

Maintenance notes

Reviewed 2014-06-06: Made all the -Walls go away.


Reviewed 2013-11-24.
Reviewed 2012-11-24: Moved to ABR.File.Lock from ABR.LockFile.

35.2

Basic lock operations

lockFile path locks the file at path, returning True iff the file was
not already locked and was successfully locked. unlockFile path
unlocks the file at path, returning True iff the file was locked and
was successfully unlocked. isLockedFile path returns True iff the
file at path is locked.
lockFile, unlockFile, isLockedFile :: String -> IO Bool
lockFile filename = do
islocked <- isLockedFile filename
if islocked
then return False
else do writeFile (extend filename) "LOCKED\n"
return True
unlockFile filename = do
islocked <- isLockedFile filename
if islocked
then do removeFile (extend filename)
return True
else return False
isLockedFile filename = do
f <- E.catch (readFile (extend filename))
(\(_ :: E.IOException) -> return "")
return (length f > 0)
extend :: String -> String
extend filename = filename ++ ".LOCK"

35.3

Multiple file operations

areAnyLocked fs returns True iff at least one of the files named in


fs is locked.
areAnyLocked :: [String] -> IO Bool
areAnyLocked []
= return False
areAnyLocked (f:fs) = do
l <- isLockedFile f
if l then return True
else areAnyLocked fs
lockFiles fs locks all files named in fs. unlockFiles fs unlocks
all files named in fs.
lockFiles, unlockFiles :: [String] -> IO ()
lockFiles
fs = mapM_ lockFile
fs
unlockFiles fs = mapM_ unlockFile fs

47

35.4

Guards

lockGuard directory fs handler process checks whether


any of the files fs in directory are locked. If any
one is, handler is executed, otherwise process is executed.
blockGuard directory fs handler process checks whether any of the
files fs in directory are locked. If any one is, handler is executed,
otherwise the files are locked, process is executed, then the files are
unlocked again.
lockGuard, blockGuard ::
String -> [String] -> IO () -> IO () -> IO ()
lockGuard directory fs handler process = do
anyLocked <- areAnyLocked $ map (directory ++) fs
if anyLocked then handler
else process
blockGuard directory fs handler process = do
let fs = map (directory ++) fs
anyLocked <- areAnyLocked fs
if anyLocked
then handler
else do lockFiles fs
process
unlockFiles fs
{-# language ScopedTypeVariables #-}

36

File.Versions

The ABR.Fie.Versions module provides replacements for


Prelude.readFile and Prelude.writeFile that read the most recent and write the next version of a file. Each version of a file is
distinguished by a .number extension appended to the root name
of the file. It also provides some IO utilities.
module ABR.File.Versions (
readLatest, writeNew, writeNew, writeNew,
purgeVersions, getNames, readFile, writeFile,
removeR, createDirectory, removeVersions,
latestDate
) where

latestDate :: FilePath -> FilePath -> IO (Maybe String)


latestDate dir root = do
mn <- latestName dir root
case mn of
Nothing
->
return Nothing
Just name -> do
t <- fileModTime (dir ++/++ name)
return $ Just t

36.4

Write the next version

writeNew dir root content binary writes content to a new version


of the file with the given root file name in directory dir . The new file
is assigned the access permissions of dir (masked by -rw-rw-rw-)
and the same userID and groupID as dir . Iff binary no text encoding
is used.
writeNew :: FilePath -> FilePath -> String -> Bool -> IO ()
writeNew dir root content binary = do
mn <- nextName dir root
let name = case mn of
Nothing -> root ++.++ "0"
Just n -> n
writeFile dir name content binary
writeNew dir root content mode binary writes content to a new
version of the file with the given root file name in directory dir .
The new file has the given access mode and the same userID and
groupID as dir . Iff binary no text encoding is used.
writeNew ::
FilePath -> FilePath -> String -> FileMode -> Bool -> IO ()
writeNew dir root content mode binary = do
mn <- nextName dir root
let name = case mn of
Nothing -> root ++.++ "0"
Just n -> n
writeFile dir name content binary
setFileMode (dir ++/++ name) mode

writeNew dir root content binary writes content to a new version


Data.Char
of the file with the given root file name in directory dir . The new file
Data.List
has the access mode -rw------- and the same userID and groupID
System.Directory
as dir . Iff binary no text encoding is used.
System.Posix hiding (createDirectory, removeDirectory)
qualified Control.Exception as E
writeNew :: FilePath -> FilePath -> String -> Bool -> IO ()
System.IO
writeNew dir root content binary = do
mn <- nextName dir root
import ABR.Text.String
let name = case mn of
import ABR.Util.Time
Nothing -> root ++.++ "0"
Just n -> n
writeFile dir name content binary
36.1 Maintenance notes
setFileMode (dir ++/++ name) $
unionFileModes ownerReadMode ownerWriteMode
Reviewed 2014-11-10: Fixed binary submission in Virgil. Reviewed
2014-06-06: Made all the -Walls go away.
Reviewed 2013-11-24.
Reviewed 2012-11-24: Moved to ABR.File.
36.5 Purge old versions
import
import
import
import
import
import

36.2

Read the latest version

readLatest dir root reads the contents of the latest version of the
file with the given root file name in directory dir . Either Just the
contents are returned, or Nothing if no version of the file could be
read.
readLatest :: FilePath -> FilePath -> IO (Maybe String)
readLatest dir root = do
mn <- latestName dir root
case mn of
Nothing -> return Nothing
Just name -> readFile (dir ++/++ name)

36.3

Date of the latest version

latestDate dir root returns a String containing the modification


date of the latest version, if one exists.

April 3, 2015

purgeVersions dir root deletes all old versions of the file with the
given root name in directory dir .
purgeVersions :: FilePath -> FilePath -> IO ()
purgeVersions dir root = do
names <- getNames dir root
let names = names \\ [latest root names]
mapM_ (removeFile . (dir ++/++)) names

36.6

Remove all versions

removeVersions dir root deletes all versions of the file with the
given root name in directory dir .
removeVersions :: FilePath -> FilePath -> IO ()
removeVersions dir root = do
names <- getNames dir root
mapM_ (removeFile . (dir ++/++)) names

48

36.7

Get all versions

if existsAsD
then do
contents <- getDirectoryContents path
mapM_ (removeR . (path ++/++)) $
filter (any (/= .)) contents
removeDirectory path
else return ()

getNames dir root returns the list of filenames in directory dir that
contain the given root file name and a version number.
getNames :: FilePath -> FilePath -> IO [String]
getNames dir root = do
ls <- E.catch (getDirectoryContents dir)
(\(_ :: E.IOException) ->
error $ "Error: Directory " ++ dir
++ " can not be read.")
return $ filter (match root) ls
where
match :: String -> String -> Bool
match xs ys =
let n = length xs
in xs == take n ys && extOnly (drop n ys)
extOnly :: String -> Bool
extOnly (.:xs)
= length xs > 0 && and (map isDigit xs)
extOnly _
= False

36.8

Read and write file bottlenecks

readFile path provides a safe way to read a file without raising


an exception if the file does not exist. It returns Nothing if the file
could not be read, Just contents otherwise.
readFile :: FilePath -> IO (Maybe String)
readFile filename = do
f <- E.catch (readFile filename)
(\(_ :: E.IOException) -> return "\0")
if f == "\0"
then return Nothing
else return (f seq Just f)

createDirectory parentDir newDir creates a directory, newDir


inside parentDir . If newDir already exists (as a file or directory) it
is removed (along with its contents) first. newDir is assigned the
same userID groupID and access permissions as parentDir .
createDirectory :: FilePath -> FilePath -> IO ()
createDirectory parentDir newDir = do
let path = parentDir ++/++ newDir
existsAsF <- doesFileExist path
existsAsD <- doesDirectoryExist path
if existsAsF || existsAsD
then removeR path
else return ()
createDirectory path
stat <- getFileStatus parentDir
let parentMode = fileMode stat
parentOwner = fileOwner stat
parentGroup = fileGroup stat
E.catch (do
setFileMode path parentMode
setOwnerAndGroup path parentOwner parentGroup
) (\(_ :: E.IOException) ->
return ()
)

36.10

Private routines

latest :: FilePath -> [FilePath] -> FilePath


latest fName
= foldl1 later
where
later :: String -> String -> String
later xs ys
| nxs < nys = ys
writeFile :: FilePath -> FilePath -> String -> Bool -> IO ()
| otherwise = xs
writeFile dir name content binary = do
where
let path = dir ++/++ name
nxs, nys :: Int
if binary then do h <- openFile path WriteMode
nxs = read $ drop (length fName + 1) xs
hSetBinaryMode h binary
nys = read $ drop (length fName + 1) ys
hPutStr h content
hClose h
latestName :: FilePath -> FilePath -> IO (Maybe FilePath)
else do writeFile path content
latestName dirPath fName = do
stat <- getFileStatus dir
ls <- getNames dirPath fName
let dirMode = fileMode stat
case ls of
dirOwner = fileOwner stat
[] -> return Nothing
dirGroup = fileGroup stat
_ -> return $ Just $ latest fName ls
mask = foldl1 unionFileModes [
nextName :: FilePath -> FilePath -> IO (Maybe FilePath)
ownerReadMode, ownerWriteMode,
nextName dirPath fName = do
groupReadMode, groupWriteMode,
oldVer <- latestName dirPath fName
otherReadMode, otherWriteMode]
case oldVer of
newMode = intersectFileModes
Nothing -> return Nothing
mask dirMode
Just oldVer -> do
E.catch (do
let n = length fName
setFileMode path newMode
v :: Int = read $ drop (n+1) oldVer
setOwnerAndGroup path dirOwner dirGroup
return $ Just $ fName ++ "." ++ show (v + 1)
) (\(_ :: E.IOException) ->
return ()
)
writeFile dir file content binary writes content to dir /file
The new file is assigned the access permissions of dir (masked by
-rw-rw-rw-) and the same userID and groupID as dir . Iff binary
no text encoding is used.

37

36.9

Creating and removing directories

removeR path removes a file or directory, path, first, recursively


removing all its contents if it is a directory. If path does not exists,
nothing is done.
removeR :: FilePath -> IO ()
removeR path = do
existsAsF <- doesFileExist path
if existsAsF
then removeFile path
else do
existsAsD <- doesDirectoryExist path

April 3, 2015

CGI

{-# language ScopedTypeVariables #-}


Module ABR.CGI implements support for Common Gateway Interface (CGI) programming.
module ABR.CGI (
mimeHeader, printMimeHeader, docType, printDocType,
put, put, HTag, HAttributes, baseE_, isindexE_,
linkE_, metaE_, nextidE_, inputE_, hrE_, brE_, imgE_,
isindexN_, hrN_, brN_, htmlE, headE, titleE, styleE,
bodyE, addressE, blockquoteE, formE, selectE,
optionE, dlE, dtE, ddE, olE, ulE, dirE,
menuE, liE, pE, preE, aE, mapE, areaE, citeE,

49

HTML elements can have a list of attributes ( HAttributes ) of the


form name=value.

codeE, emE, kbdE, sampE, strongE, varE, bE,


iE, ttE, uE, tableE, captionE, trE, thE, tdE,
divE, subE, supE, centerE, fontE, smallE,
bigE, textareaE, h1E, h2E, h3E, h4E, h5E,
h6E, htmlN, headN, titleN, bodyN, addressN,
blockquoteN, dlN, dtN, ddN, olN, ulN, dirN,
menuN, liN, pN, preN, citeN, codeN, emN,
kbdN, sampN, strongN, varN, bN, iN, ttN, uN,
tableN, captionN, trN, thN, tdN, subN, supN,
centerN, smallN, bigN, h1N, h2N, h3N, h4N,
h5N, h6N, htmlT, htmlError, getQueryString,
getPathInfo, getScriptName,
getScriptDirectory, getContentLength,
getFormData, getFormData, dumpFormData
) where
import
import
import
import
import
import
import
import

type HAttributes = [(String,String)]


printAttributes atts prints the attribute list atts, with spaces, =s
and "s as needed.
printAttributes :: HAttributes -> IO ()
printAttributes as = putStr $
unwords [a ++ "=\"" ++ v ++ "\"" | (a,v) <- as]
Some HTML elements are empty and do not contain text.
emptyElement tag attributes prints a HTML element with the given
tag and attributes.
emptyElement :: HTag -> HAttributes -> IO ()
emptyElement tag attrs = do
putChar <
putStr tag
putChar
printAttributes attrs
putStr " />"

Data.Char
System.Environment
Numeric
Data.List
Control.Monad
qualified Control.Exception as E
qualified Data.Map as M
System.IO

Most elements enclose other elements or text.


element tag attributes contents prints the tag, attributes,
whatever is printed by contents and then a closing tag.

import ABR.Data.List
import ABR.Text.Markup

37.1

Maintenance notes

Reviewed 2015-02-09. Passed hlint.


Reviewed 2014-05-28: Made all -Walls go away.
Reviewed 2013-11-25.
Reviewed 2012-11-05: Removed dependence on ABR.Data.BSTree.
Reviewed 2009-11-27: Yes there is a standard library for XHTML
generation, but it is not so compellingly better than this that Im
ready to deprecate this. Am updating so that the generated code is
XHTML-Transitional.

37.2

37.6

Mime header

First things first. A CGI tool should print the magic lines identifying
the output as HTML. mimeHeader is the MIME header text, which
is printed by printMimeHeader .
mimeHeader :: String
mimeHeader = "Content-type: text/html\n"
printMimeHeader :: IO ()
printMimeHeader = putStrLn mimeHeader

37.3

Document type

Identify the kind of html being generated. docType is printed by


which is printed by printDocType .
docType :: String
docType = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0\
\ Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD\
\/xhtml1-transitional.dtd\">"
printDocType :: IO ()
printDocType = putStrLn docType

37.4

put, put :: String -> IO ()


put = putStr . makeHTMLSafe
put = putStr . makeHTMLSafe

37.5

HTML elements (generic)

HTML elements have a name (a HTag ).


type HTag = String

April 3, 2015

HTML elements (specific shortcuts)

This is not an exhaustive list. Add more as needed.


tagE attributes prints an empty element its tag and attributes.
baseE_, isindexE_, linkE_, metaE_, nextidE_,
inputE_, hrE_, brE_, imgE_
:: HAttributes -> IO ()
baseE_
= emptyElement "base"
isindexE_ = emptyElement "isindex"
linkE_
= emptyElement "link"
metaE_
= emptyElement "meta"
nextidE_ = emptyElement "nextid"
inputE_
= emptyElement "input"
hrE_
= emptyElement "hr"
brE_
= emptyElement "br"
imgE_
= emptyElement "img"
tagN

prints an empty element with its tag and no attributes.

isindexN_, hrN_, brN_


:: IO ()
isindexN_ = isindexE_ []
hrN_
= hrE_
[]
brN_
= brE_
[]
tagE attributes contents prints a non-empty element with its tag,
attributes and contents.

Special character encoding

put cs prints cs with all special characters encoded.


encodes all control characters.

element :: HTag -> HAttributes -> IO a -> IO a


element tag attrs stuff = do
putChar <
putStr tag
unless (null attrs) (putChar )
printAttributes attrs
putChar >
returned <- stuff
putStr "</"
putStr tag
putChar >
return returned

put cs

htmlE, headE, titleE, styleE, bodyE, addressE, blockquoteE,


formE, selectE, optionE, dlE, dtE, ddE, olE,
ulE, dirE, menuE, liE, pE, preE, aE, mapE,
areaE, citeE, codeE, emE, kbdE, sampE, strongE,
varE, bE, iE, ttE, uE, tableE, captionE, trE,
thE, tdE, divE, subE, supE, centerE, fontE,
smallE, bigE, textareaE, h1E, h2E, h3E, h4E,
h5E, h6E
:: HAttributes -> IO () -> IO ()
htmlE
= element "html"
headE
= element "head"
titleE
= element "title"
styleE
= element "style"

50

bodyE
addressE
blockquoteE
formE
selectE
optionE
dlE
dtE
ddE
olE
ulE
dirE
menuE
liE
pE
preE
aE
mapE
areaE
citeE
codeE
emE
kbdE
sampE
strongE
varE
bE
iE
ttE
uE
tableE
captionE
trE
thE
tdE
divE
subE
supE
centerE
fontE
smallE
bigE
textareaE
h1E
h2E
h3E
h4E
h5E
h6E

=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=

element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element
element

"body"
"address"
"blockquote"
"form"
"select"
"option"
"dl"
"dt"
"dd"
"ol"
"ul"
"dir"
"menu"
"li"
"p"
"pre"
"a"
"map"
"area"
"cite"
"code"
"em"
"kbd"
"samp"
"strong"
"var"
"b"
"i"
"tt"
"u"
"table"
"caption"
"tr"
"th"
"td"
"div"
"sub"
"sup"
"center"
"font"
"small"
"big"
"textarea"
"h1"
"h2"
"h3"
"h4"
"h5"
"h6"

tagN contents prints a non-empty element with its tag, contents


and no attributes.
htmlN, headN, titleN, bodyN, addressN, blockquoteN,
dlN, dtN, ddN, olN, ulN, dirN, menuN, liN, pN,
preN, citeN, codeN, emN, kbdN, sampN, strongN,
varN, bN, iN, ttN, uN, tableN, captionN, trN,
thN, tdN, subN, supN, centerN, smallN, bigN,
h1N, h2N, h3N, h4N, h5N, h6N
:: IO () -> IO ()
htmlN
= htmlE
[]
headN
= headE
[]
titleN
= titleE
[]
bodyN
= bodyE
[]
addressN
= addressE
[]
blockquoteN = blockquoteE []
dlN
= dlE
[]
dtN
= dtE
[]
ddN
= ddE
[]
olN
= olE
[]
ulN
= ulE
[]
dirN
= dirE
[]
menuN
= menuE
[]
liN
= liE
[]
pN
= pE
[]
preN
= preE
[]
citeN
= citeE
[]
codeN
= codeE
[]
emN
= emE
[]

April 3, 2015

kbdN
sampN
strongN
varN
bN
iN
ttN
uN
tableN
captionN
trN
thN
tdN
subN
supN
centerN
smallN
bigN
h1N
h2N
h3N
h4N
h5N
h6N

37.7

=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=

kbdE
sampE
strongE
varE
bE
iE
ttE
uE
tableE
captionE
trE
thE
tdE
subE
supE
centerE
smallE
bigE
h1E
h2E
h3E
h4E
h5E
h6E

[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]

Standards

The html element needs to have certain attributes to meet standards. htmlT applies the attributes to go with the transitional
doctype above.
htmlT :: IO () -> IO ()
htmlT = htmlE [("xmlns", "http://www.w3.org/1999/xhtml"),
("xml:lang", "en"),
("lang", "en")]

37.8

Bailing out

If the tool wants to exit, displaying only an error message use this.
htmlError message prints an error message in HTML code. Ensure message is plain text (no tags please).
htmlError :: String -> IO ()
htmlError message = htmlT (do
headN (titleN (putStr "ERROR"))
bodyN (pN (put message))
)

37.9

CGI inputs

getEnvVar var tries to get an environment variable var , catching any


exceptions, and returning an empty string if not found or empty.
getEnvVar :: String -> IO String
getEnvVar name =
E.catch (getEnv name)
(\(_ :: E.IOException) -> return [])
getQUERY STRING returns the text after the ? in a URL.
getPATH INFO returns the extra path info after the name of the
CGI tool.

getSCRIPT NAME returns the URL of the CGI tool.

getScriptDirectory returns the URL of the directory the CGI


binary is in.
getQueryString, getPathInfo, getScriptName,
getScriptDirectory :: IO String
getQueryString = getEnvVar "QUERY_STRING"
getPathInfo = getEnvVar "PATH_INFO"
getScriptName = getEnvVar "SCRIPT_NAME"
getScriptDirectory = do
script <- getScriptName
return ((reverse . dropWhile (/= /) . reverse)
script)
getCONTENT LENGTH returns the number of bytes of content arriving
via standard input.

51

getContentLength :: IO Int
getContentLength = do
contentLength <- getEnvVar "CONTENT_LENGTH"
return (if null contentLength
then 0
else read contentLength)
getFormData reads standard input to obtain the post method inputs, decodes them and returns them in a binary search tree.
getFormData :: IO (M.Map String String)
getFormData = do
contentLength <- getContentLength
cs <- getContents
return $ M.fromList $ decodeForm $
take contentLength cs
where
decodeForm :: String -> [(String,String)]
decodeForm = map nv . chop &
nv :: String -> (String, String)
nv cs =
let [name,value] = case chop = cs of
[n,v] -> [n,v]
[n] -> [n, ""]
_
-> error $ "Cant decode: " ++
show cs
in (decode name, decode value)
decode :: String -> String
decode cs = case cs of
[]
->
[]
+ : xs
->
: decode xs
% : x1 : x2 : xs ->
toEnum ((fst . head . readHex) [x1,x2])
: decode xs
x : xs
->
x : decode xs
getFormData reads standard input to obtain the post method inputs in the enctype="multipart/form-data" format, decodes them
and returns them in a binary search tree.4
getFormData :: IO (M.Map String String)
getFormData = do
contentLength <- getContentLength
hSetBinaryMode stdin True
cs <- getContents
let cs = take contentLength cs
return $ M.fromList $ decodeForm cs
where
decodeForm :: String -> [(String, String)]
decodeForm cs =
let cs = dropWhile (/= -) cs
(bs, cs) = break isControl cs
css = init (chops ("\r\n" ++ bs) cs)
in concatMap pairs css
pairs :: String -> [(String,String)]
pairs cs =
let cs = dropWhile (/= ;) cs
" : cs = dropWhile (/= ") cs
(name,cs) = break (== ") cs
fn = "\"; filename=\""
in if fn isPrefixOf cs then
let ds = drop (length fn) cs
(filename,ds) = break (== ") ds
ds = drop 2 (dropWhile (/= :) ds)
(mimetype,ds) = break isSpace ds
in [(name, drop 4 ds),
(name ++ ".filename", filename),
(name ++ ".type", mimetype)]
else
[(name, drop 5 cs)]
For debugging forms: dumpFormData tree outputs the form inputs
in a nicely encoded XHTML fragment.
4 This function in part by Annie Lo, 2134CIT Programming
Paradigms and Languages, Advanced Studies project, 2004.

April 3, 2015

dumpFormData :: M.Map String String -> IO ()


dumpFormData tree = do
let nvs = M.toList tree
tableN (do
let row (n,v) = trN (do
tdN $ put n
tdN $ put v
)
mapM_ row nvs
)

38

Daytime

Module ABR.Daytime provides time of day and weekday manipulations.


module ABR.Daytime (
Daytime(..), Weekday(..), daytimeL, weekdayP,
daytimeP, dayAndTimeP, showDT24, inInterval,
tomorrow, yesterday
) where
import Data.Char
import ABR.Parser
import ABR.Parser.Lexers
import ABR.Text.String

38.1

Maintenance notes

Reviewed 2014-06-09: Made all the -Walls go away.


Reviewed 2013-11-24.

38.2

Language tweaks

undefined :: String -> a


undefined label = error $
"Intentional undefined at module ABR.Daytime, \
\ with label \"" ++ label ++ "\"."

38.3

Data types

A Daytime consists of: hours, dtHrs ; minutes, dtMins ; and


seconds, dtSecs .
data Daytime = Daytime {
dtHrs :: Int,
dtMins :: Int,
dtSecs :: Int
} deriving (Eq, Ord)
A Weekday is one of: Sunday ; Monday ; Tuesday ; Wednesday ;
Thursday ; Friday ; or Saturday .
data Weekday =
Sunday | Monday | Tuesday | Wednesday | Thursday |
Friday | Saturday
deriving (Eq, Ord, Enum, Show)

38.4

Lexing

daytimeL recognizes the numbers words and symbols that might


occur in a day and/or time specification.
daytimeL :: Lexer
daytimeL = dropWhite $ listL [
whitespaceL,
cardinalL,
literalL : %> ":",
literalL . %> ".",
literalL , %> " ",
(some (satisfyL isAlpha "") &%> "")
@> (\[((_,w),p)] -> [(("w", map toLower w),p)])
]

52

38.5

Parsing

A weekday has this case-insensitive syntax:

<|> literalP "w" "sat"


<|> literalP "w" "saturday"

#> Saturday
#> Saturday

This type is used to store an AM/PM designation.


weekday ::=
| "m" |
|
| "w" |
|
| "f" |
|

"su"
"mo"
"tu"
"we"
"th"
"fr"
"sa"

|
|
|
|
|
|
|

"sun"
"mon"
"tues"
"wed"
"thurs"
"fri"
"sat"

|
|
|
|
|
|
|

"sunday"
"monday"
"tuesday"
"wednesday"
"thursday"
"friday"
"saturday".

data AMPM = AM | PM deriving (Eq, Ord, Show)


An AM/PM designation has this case-insensitive syntax:
ampm ::=

"am" | "a" "." "m" "."


| "pm" | "p" "." "m" ".".
ampm

weekday
su

am

sun

sunday

pm

mo

ampmP :: Parser AMPM


ampmP =
literalP "w" "am" #> AM
<|>
literalP "w" "a"
<&> literalP "." "."
<&> literalP "w" "m"
<&> literalP "." "."
#> AM
<|> literalP "w" "pm" #> PM
<|>
literalP "w" "p"
<&> literalP "." "."
<&> literalP "w" "m"
<&> literalP "." "."
#> PM

mon
monday
tu
tues
tuesday
w
we
wed

The hours in a daytime are either in 12 or 24 hour formats. Minutes


and seconds are preceded by either a colon or a period and are
between 0 and 59.

wednesday
th
thurs

hours12 ::= $cardinal in [1..12]$.

thursday
hours12
f

cardinal in [1..12]

fr

hours12P :: Parser Int


hours12P = (tagP "cardinal"
@> (\(_,h,_) -> read h))
dataSatisfies (\h -> 1 <= h && h <= 12)

fri
friday
sa

hours24 ::= $cardinal in [0 .. 23]$.


sat
hours24

saturday

cardinal in [0 .. 23]

weekdayP parses a Weekday.


weekdayP :: Parser Weekday
weekdayP =
literalP "w" "su"
<|> literalP "w" "sun"
<|> literalP "w" "sunday"
<|> literalP "w" "m"
<|> literalP "w" "mo"
<|> literalP "w" "mon"
<|> literalP "w" "monday"
<|> literalP "w" "tu"
<|> literalP "w" "tues"
<|> literalP "w" "tuesday"
<|> literalP "w" "w"
<|> literalP "w" "we"
<|> literalP "w" "wed"
<|> literalP "w" "wednesday"
<|> literalP "w" "th"
<|> literalP "w" "thurs"
<|> literalP "w" "thursday"
<|> literalP "w" "f"
<|> literalP "w" "fr"
<|> literalP "w" "fri"
<|> literalP "w" "friday"
<|> literalP "w" "sa"

April 3, 2015

#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>
#>

Sunday
Sunday
Sunday
Monday
Monday
Monday
Monday
Tuesday
Tuesday
Tuesday
Wednesday
Wednesday
Wednesday
Wednesday
Thursday
Thursday
Thursday
Friday
Friday
Friday
Friday
Saturday

hours24P :: Parser Int


hours24P = (tagP "cardinal"
@> (\(_,h,_) -> read h))
dataSatisfies (\h -> 0 <= h && h <= 23)
minSec ::= (":" | ".") $cardinal in [0..59]$.
minSec
:

cardinal in [0..59]

minSecP :: Parser Int


minSecP =
(literalP ":" ":" <|> literalP "." ".") &>
(tagP "cardinal"
@> (\(_,m,_) -> read m))
dataSatisfies (\m -> 0 <= m && m <= 59)
A daytime has this syntax:
daytime ::=
hours12 [minSec [minSec]] ampm
| hours24 [minSec [minSec]].

53

daytime

(ts mod 60)

hours12

ampm
minSec

(Daytime h m s)
let ts = s tm = m in Daytime
(h - h (tm mod
(ts mod

minSec
hours24
minSec
minSec

daytimeP parses a daytime.


daytimeP :: Parser Daytime
daytimeP =
hours12P
<&> optional (minSecP <&> optional
<&> ampmP
@> (\(h,msa) -> case msa of
([],AM)
->
Daytime (h mod 12) 0 0
([],PM)
->
Daytime (h mod 12 + 12)
([(m,[])],AM) ->
Daytime (h mod 12) m 0
([(m,[])],PM) ->
Daytime (h mod 12 + 12)
([(m,[s])],AM) ->
Daytime (h mod 12) m s
([(m,[s])],PM) ->
Daytime (h mod 12 + 12)
_ -> undefined "daytimeP 1"
)
<|> hours24P
<&> optional (minSecP <&> optional
@> (\(h,ms) -> case ms of
[]
-> Daytime h 0 0
[(m,[])] -> Daytime h m 0
[(m,[s])] -> Daytime h m s
_ -> undefined "daytimeP 2"
)

signum _ = undefined "Num.signum"


minSecP)
abs _ = undefined "Num.abs"
fromInteger _ = undefined "Num.fromInteger"

0 0

m 0

tomorrow, yesterday :: Weekday -> Weekday


tomorrow d = toEnum ((fromEnum d + 1) mod 7)
yesterday d = toEnum ((fromEnum d - 1) mod 7)

m s

38.8

minSecP)

dayAndTime

daytime

weekday

dayAndTimeP parses a day and a time.


dayAndTimeP :: Parser (Weekday,Daytime)
dayAndTimeP =
weekdayP <&> daytimeP
<|> daytimeP <&> weekdayP
@> (\(d,w) -> (w,d))

38.6.1

Instance declarations
Showing

instance Show Daytime where


showsPrec _ (Daytime h m s) = showPad h
. showChar . . showPad m
. showChar . . showPad s
where
showPad = showString . rJustify 0 2 . show

38.6.2

Weekday methods

tomorrow d returns the weekday after d. yesterday d returns the


weekday after d.

dayAndTime ::=
weekday daytime
| daytime weekday.

38.6

38.7

Daytime methods

inInterval start duration time returns True iff start time <
start + duration.
inInterval :: Daytime -> Daytime -> Daytime -> Bool
inInterval start duration time =
start <= time && time < (start + duration)
showDT24 t shows the daytime t in 24 hour format suppressing the
seconds.
showDT24
showDT24
pad h
where
pad =

daytime

Arithmetic

tm div 60)
60)
60)

_ * _ = undefined "Num.*"

A day and time has this syntax:

weekday

- (Daytime h m s) =
s
m - ts div 60

39

:: Daytime -> String


(Daytime h m _) =
++ "." ++ pad m
rJustify 0 2 . show

HaskellLexer

The module ABR.HaskellLexer provides facilities to partially parse


Haskell sources.
module ABR.HaskellLexer (
deliterate, programL, offside, unlex, promoteMethods,
discardInners, moduleName, declared, declarations
) where
import Data.Char
import ABR.Util.Pos
import ABR.Parser
import ABR.Parser.Lexers hiding (floatL, stringL)

39.1

Maintenance notes

Reviewed 2014-06-10: Made all the -Walls go away.


Reviewed 2013-11-24.

39.2

Language tweaks

undefined :: String -> a


undefined label = error $
"Intentional undefined at module ABR.HaskellLexer, \
\ with label \"" ++ label ++ "\"."

instance Num Daytime where


(Daytime h m s)
let ts = s +
tm = m +
in Daytime
(h + h +
(tm mod

April 3, 2015

+ (Daytime h m s) =
s
m + ts div 60
tm div 60)
60)

39.3

Handling literate scripts

deliterate cps removes all informal text from cps, a literate


Haskell source as a list of character-position pairs as produced by
Parser.preLex. A similar list of character-position pairs is returned.
This does not remove -- or {- -} comments from within the formal
text. Those comments are handled by the lexer.

54

((\n,p):cps) -> (\n,p) : i cps


(cp:cps)
-> cp : f cps

start
>
i

}
*

\n

f9

i1

i1
i2
i3
i4
i5
i6
i7
i8
i9
i10
i11

f cps = case cps of


[]
-> []
((\\,p):cps) -> f1 [(\\,p)] cps
(cp:cps)
-> cp : f cps

*
b

e
i2
*

f8
e
i3

d
*

f7
i4
o

i
*

f6
i5

*
n

c
*

f5

i6

*
{

{
i7
*

f4
c
i8

d
*

f3

ix
ix
ix
ix
ix
ix
ix
ix
ix
ix
ix

b
e
g
i
n
{
c
o
d
e
}

i2
i3
i4
i5
i6
i7
i8
i9
i10
i11
f

fx c a rs cps = case
[]
->
((c,p):cps)
| c == c
->
| otherwise ->
f1
f2
f3
f4
f5
f6
f7
f8
f9

=
=
=
=
=
=
=
=
=

fx
fx
fx
fx
fx
fx
fx
fx
fx

e
n
d
{
c
o
d
e
}

cps of
reverse rs
a ((c,p):rs) cps
reverse rs ++ [(c,p)] ++ f cps

f2
f3
f4
f5
f6
f7
f8
f9
(\_ cps -> i cps)

39.4

This section implements a lexer for Haskell. It is essentially complete for ASCII sources, but not for unicode sources.
programL performs the lexical analysis of any Haskell source. Apply deliterate to literate sources before lexing.

i9

Lexing scripts

f2
i10

i11

f1

lexeme

program ::= {lexeme | whitespace}.


program

e
*

=
=
=
=
=
=
=
=
=
=
=

n
*

ix c a cps = case cps of


[]
-> []
((c,_):cps) | c == c
-> a cps
| otherwise -> i cps

whitespace

programL :: Lexer
programL = listL [hwhitespaceL, lexemeL]

f
*

Figure 3: The finite state machine for deliterating Haskell sources.

lexeme ::=
varid | conid | varsym | consym
| reservedop | reservedid.

| literal | special

lexeme
varid

deliterate :: [(Char, Pos)] -> [(Char, Pos)]

conid

Figure 3 shows a finite state machine that has been extended to


include LATEX literate scripts. In that figure > is a > at the
start of a line, * represents any character other than those that are
shown to lead to other states. All states are accepting states. It is
implemented as follows:

varsym

reservedop

->
->
->
->

f cps = case cps of


[]
-> []

April 3, 2015

literal
special

deliterate = i
where
i cps = case cps of
[]
((>,(_,0)):cps)
((\\,_):cps)
(_:cps)

consym

[]
f cps
i1 cps
i cps

reservedid

lexemeL :: Lexer
lexemeL =
varidL <|> conidL <|> varsymL <|> consymL
<|> hliteralL <|> specialL <|> reservedopL
<|> reservedidL

55

literal ::= integer | float | char | string.


literal
integer

whitecharL :: Lexer
whitecharL =
newlineL <|> vertabL <|> formfeedL <|> spaceL
<|> tabL <|> nonbrkspcL
These are already defined in Parser.lhs.

float

newline ::= "\\n".

char
newline
string

\n

hliteralL :: Lexer
hliteralL = integerL <|> floatL <|> charL <|> stringL

space ::= " ".

special ::=
"(" | ")" | "," | ";" | "[" | "]" | "_" | "" | "{"
| "}".

space

tab ::= "\\t".

special
(

tab
\t

)
,

vertab ::= "\\v".

vertab

\v

formfeed ::= "\\f".

_
formfeed

\f

nonbrkspc ::= $non-breaking space$.

specialL :: Lexer
specialL = (
literalL
<|> literalL
<|> literalL
<|> literalL
) %> "special"

nonbrkspc
non-breaking space

(
;
_
}

<|> literalL )
<|> literalL [
<|> literalL

<|> literalL ,
<|> literalL ]
<|> literalL {

nonbrkspcL :: Lexer
nonbrkspcL = failA "non-breaking spaces not implemented"
comment ::= "--" {any1} newline.
comment

whitespace ::= whitestuff {whitestuff}.

-whitespace

newline
any1

whitestuff
whitestuff

hwhitespaceL :: Lexer
hwhitespaceL = (some whitestuffL) &%> " "
whitestuff ::= whitechar | comment | ncomment.
whitestuff

commentL :: Lexer
commentL = tokenL "--" <& many any1L <& newlineL
ncomment ::= "{-" ANYseq {ncomment ANYseq} "-}".
ncomment
{-

ANYseq

-}
ncomment

ANYseq

whitechar
comment
ncomment

whitestuffL :: Lexer
whitestuffL =
(whitecharL <|> commentL <|> ncommentL) %> " "
whitechar ::=
newline | vertab | formfeed | space | tab | nonbrkspc.

ncommentL :: Lexer
ncommentL =
tokenL "{-" <& aNYseqL
<& soft (many (ncommentL <& aNYseqL)) <& tokenL "{-"
ANYseq ::= <{any2}!{any2}("{-" | "-}") {any2}>.
ANYseq
any2

whitechar

not

newline
vertab

{any2

-}

any2

formfeed
space
tab
nonbrkspc

April 3, 2015

aNYseqL :: Lexer
aNYseqL =
soft (manyUntil any2L (tokenL "{-" <|> tokenL "-}"))
any2 ::= any1 | newline | vertab | formfeed.

56

any2

large

any1

ASClarge

newline

ISOlarge

vertab
formfeed

any2L :: Lexer
any2L = any1L <|> newlineL <|> vertabL <|> formfeedL

largeL :: Lexer
largeL = aSClargeL <|> iSOlargeL
ASClarge ::= "A" | "B" | $...$ | "Z".
ASClarge

any1 ::= graphic | space | tab | nonbrkspc.

any1

graphic

...

space

tab
nonbrkspc

any1L :: Lexer
any1L = graphicL <|> spaceL <|> tabL <|> nonbrkspcL

aSClargeL :: Lexer
aSClargeL = satisfyL isUpper "ASClarge"
ISOlarge ::= $upper case accented letters$.
ISOlarge

graphic ::=
large | small | digit | symbol | special | ":"
| "\""
| "".
graphic
large

upper case accented letters

iSOlargeL :: Lexer
iSOlargeL = failA "ISOlarge not implemented"
symbol ::=

ASCsymbol | ISOsymbol.

small

symbol

digit

ASCsymbol

symbol

ISOsymbol

special
:

symbolL :: Lexer
symbolL = aSCsymbolL <|> iSOsymbolL
ASCsymbol ::=
"!" | "#" | "$" | "%" | "&" | "*" | "+" | "."
| "/" | "<" | "=" | ">" | "?" | "@" | "\\" | "^"
| "|" | "-" | "~".

"

graphicL :: Lexer
graphicL =
largeL <|> smallL <|> digitL <|> symbolL <|> specialL
<|> literalL : <|> literalL " <|> literalL \

ASCsymbol
!
#

small ::= ASCsmall | ISOsmall.

$
small
ASCsmall

ISOsmall

&
*

smallL :: Lexer
smallL = aSCsmallL <|> iSOsmallL

ASCsmall ::= "a" | "b" | $...$ | "z".

ASCsmall

<

...

>

?
@

aSCsmallL :: Lexer
aSCsmallL = satisfyL isLower "ASCsmall"

ISOsmall ::= $lower case accented letter$.

ISOsmall

lower case accented letter


-

iSOsmallL :: Lexer
iSOsmallL = failA "ISOsmall not implemented"
large ::= ASClarge | ISOlarge.

April 3, 2015

aSCsymbolL :: Lexer
aSCsymbolL =

57

<|>
<|>
<|>
<|>
<|>
<|>

literalL
literalL
literalL
literalL
literalL
literalL
literalL

!
%
+
<
?
^
~

<|>
<|>
<|>
<|>
<|>
<|>

literalL
literalL
literalL
literalL
literalL
literalL

#
&
.
=
@
|

<|>
<|>
<|>
<|>
<|>
<|>

literalL
literalL
literalL
literalL
literalL
literalL

$
*
/
>
\\
-

ISOsymbol ::= $unprintable junk$.


ISOsymbol
unprintable junk

varidL :: Lexer
varidL = (
smallL <&&>
((many (smallL <|> largeL <|> digitL
<|> literalL \ <|> literalL _)) &%> "")
) %> "varid"
varidL :: Lexer
varidL =
dataSatisfies varidL
(\(((_,ident),_):_) -> not (ident elem reservedids))
conid ::= large {small | large | digit | "" | "_"}.

iSOsymbolL :: Lexer
iSOsymbolL = failA "ISOsymbol not implemented"

conid
large
small

digit ::= "0" | "1" | $...$ | "9".

large
digit
0

digit

...
9

digitL :: Lexer
digitL = satisfyL isDigit "digit"
octit ::= "0" | "1" | $...$ | "7".

conidL :: Lexer
conidL = (
largeL <&&>
((many (smallL <|> largeL <|> digitL <|> literalL \
<|> literalL _)) &%> "")
) %> "conid"
reservedid ::=
"case"
| "deriving"
| "import"
| "infixr"
| "newtype"
| "where".

octit
0
1
...
7

octitL :: Lexer
octitL = satisfyL (\c -> c >= 0 && c <= 7) "octit"
hexit ::= digit | "A" | $...$ | "F" | "a" | $...$ | "f".

|
|
|
|
|

"class"
"do"
"in"
"instance"
"of"

|
|
|
|
|

"data"
"else"
"infix"
"let"
"then"

|
|
|
|
|

"default"
"if"
"infixl"
"module"
"type"

reservedid
case
class
data

hexit
digit

default
deriving

do
...
else

if

import
...
in

infix

hexitL :: Lexer
hexitL = satisfyL isHexDigit "hexit"
varid ::=
<small {small | large | digit | "" | "_"}
! reservedid>.

infixl
infixr
instance
let

varid

module

small
small
large
digit

_
not
reservedid

April 3, 2015

newtype
of
then
type
where

reservedids :: [String]
reservedids = [
"case",
"class",

"data",

"default",

58

"deriving",
"import",
"infixr",
"newtype",
"where"

"do",
"in",
"instance",
"of",

"else",
"infix",
"let",
"then",

"if",
"infixl",
"module",
"type",

]
reservedidL :: Lexer
reservedidL = (
dataSatisfies varidL
(\(((_,idn),_):_) -> idn elem reservedids)
) %> "reservedid"

reservedopL :: Lexer
reservedopL = (
tokenL ".." <|> tokenL "::" <|> tokenL "=>"
<|> tokenL "=" <|> tokenL "\\" <|> tokenL "|"
<|> tokenL "<-" <|> tokenL "->" <|> tokenL "@"
<|> tokenL "~"
) %> "reservedop"
decimal ::= digit {digit}.
decimal
digit

varsym ::= <symbol {symbol | ":"} ! reservedop>.


varsym

digit

decimalL :: Lexer
decimalL = (some digitL) &%> "decimal"

symbol
symbol

octal ::= octit {octit}.

octal

not

octit

reservedop

varsymL :: Lexer
varsymL = ( symbolL <&&>
((many (symbolL <|> literalL :)) &%> "")
) %> "varsym"

octit

octalL :: Lexer
octalL = (some octitL) &%> "octal"
hexadecimal ::= hexit {hexit}.

varsymL :: Lexer
varsymL =
dataSatisfies varsymL
(\(((_,sym),_):_) -> not (sym elem reservedops))
consym ::= <":" {symbol | ":"} ! reservedop>.
consym
:
symbol

hexadecimal
hexit
hexit

hexadecimalL :: Lexer
hexadecimalL = (some hexitL) &%> "hexadecimal"
integer ::= decimal
| "0o" octal | "0O" octal
| "0x" hexadecimal | "0X" hexadecimal.
integer

decimal

not
reservedop

consymL :: Lexer
consymL = ( literalL : <&&>
((many (symbolL <|> literalL :)) &%> "")
) %> "varsym"
consymL :: Lexer
consymL =
dataSatisfies consymL
(\(((_,sym),_):_) -> not (sym elem reservedops))
reservedop ::=
".." | "::" | "=" | "\\" | "|" | "<-" | "->"
| "@" | "~" | "=>".
reservedop
..
::

0o

octal

0O

octal

0x

hexadecimal

0X

hexadecimal

integerL :: Lexer
integerL = (
decimalL
<|> (literalL 0 <&&>
<|> (literalL 0 <&&>
<|> (literalL 0 <&&>
hexadecimalL)
<|> (literalL 0 <&&>
hexadecimalL)
) %> "integer"

literalL o <&&> octalL)


literalL O <&&> octalL)
literalL x <&&>
literalL X <&&>

float ::= decimal "." decimal


\ [("e" | "E")[ "-" |"+"] decimal].

=
float
\

decimal

decimal

|
<-

decimal
-

->

E
+

@
~
=>

reservedops :: [String]
reservedops = ["..", "::", "=", "\\", "|", "<-", "->",
"@", "~", "=>"]

April 3, 2015

floatL :: Lexer
floatL = (
decimalL <&&> literalL . <&&> decimalL <&&>
((optional ((literalL e <|> literalL E)
<&&> ((optional (literalL - <|> literalL +)
) &%> ""
)

59

<&&> decimalL
)

escape
\

charesc

) &%> ""
)

ascii

) %> "float"

decimal

char ::= "" (<graphic ! "" | "\\"> | space |


<escape ! "\\&">) "".

octal

hexadecimal

char

graphic

not

escapeL :: Lexer
escapeL = (
literalL \\
<&&> (
charescL
<|> asciiL
<|> decimalL
<|> (literalL o <&&> octalL)
<|> (literalL x <&&> hexadecimalL)
)
) %> "escape"

\
space
escape
not
\&

charL :: Lexer
charL = (
literalL \
<&&> (
alsoNotSat
<|> spaceL
<|> alsoNotSat
)
<&&> literalL \
) %> "char"
where
not1L :: Lexer
not1L = literalL \
not2L :: Lexer
not2L = literalL \\

graphicL not1L

charesc ::=
"a" | "b" | "f" | "n" | "r" | "t" | "v" | "\"
| """ | "" | "&".

escapeL not2L

charesc

<|> literalL \\

<&&> literalL &

b
f

string ::= "\"" {<graphic ! "\"" | "\\"> | space |


escape | gap} "\"".

n
r

string

"

"
v

graphic
not

"

"

&

space
escape
gap

stringL :: Lexer
stringL = (
literalL "
<&&> ((many (
alsoNotSat graphicL not1L
<|> spaceL
<|> escapeL
<|> gapL
)
) &%> ""
)
<&&> literalL "
) %> "string"
where
not1L :: Lexer
not1L = literalL " <|> literalL \\
escape ::=

"\\" ( charesc | ascii | decimal | "o" octal


| "x" hexadecimal).

April 3, 2015

charescL :: Lexer
charescL = (
literalL
<|> literalL
<|> literalL
<|> literalL
) %> "charesc"

ascii ::=
"^" cntrl
| "NUL" | "SOH"
| "ACK" | "BEL"
| "FF" | "CR"
| "DC2" | "DC3"
| "CAN" | "EM"
| "US" | "SP"

a
n
v
\

|
|
|
|
|
|

<|>
<|>
<|>
<|>

literalL
literalL
literalL
literalL

"STX" |
"BS" |
"SO" |
"DC4" |
"SUB" |
"DEL".

"ETX"
"HT"
"SI"
"NAK"
"FS"

|
|
|
|
|

b <|> literalL f
r <|> literalL t
\\ <|> literalL "
&

"EOT"
"LF"
"DLE"
"SYN"
"GS"

|
|
|
|
|

"ENQ"
"VT"
"DC1"
"ETB"
"RS"

60

cntrl

ascii
^

ASClarge

cntrl

NUL

SOH

STX

ETX

EOT

ENQ

ACK

cntrlL :: Lexer
cntrlL = (
aSClargeL
<|> literalL @ <|> literalL [
<|> literalL \\ <|> literalL ] <|> literalL ^
<|> literalL _
) %> "cntrl"

BEL
BS
HT
LF

gap ::= "\\" whitechar {whitechar} "\\".

VT
gap
FF

whitechar

\
whitechar

CR

gapL :: Lexer
gapL = (
literalL \\
<&&> ((some whitecharL) &%> "")
<&&> literalL \\
) %> "gap"

SO
SI
DLE
DC1

39.5

DC2

offside tlps applies the off-side layout rule, inserting braces and
semicolons. tlps is a list of tag-lexeme-position tuples produced by
the lexer (programL) and after all whitespace has been removed with
dropwhite. Note that scripts either start with an explicit or implicit
module header. Either case is properly handled, as is the case of
unexpectedly short scripts.

DC3
DC4
NAK
SYN

offside :: TLPs -> TLPs


offside []
= []
offside [x]
= [x]
offside ((("reservedid", "module"),pos):tlps)
= os [] ((("reservedid", "module"),pos):tlps)
offside (((tag, lexeme),(line,column)):tlps)
=
(("special","{"),(line,column))
: ((tag, lexeme),(line,column))
: os [column] tlps

ETB
CAN
EM
SUB
FS
GS
RS

This function does the real work. The list of Ints is the stack of
pending column numbers defining on/off-side.

US
SP
DEL

asciiL :: Lexer
asciiL = (
(literalL ^ <&&> cntrlL)
<|> tokenL "NUL" <|> tokenL "SOH"
<|> tokenL "ETX" <|> tokenL "EOT"
<|> tokenL "ACK" <|> tokenL "BEL"
<|> tokenL "LF" <|> tokenL "VT"
<|> tokenL "CR" <|> tokenL "SO"
<|> tokenL "DLE" <|> tokenL "DC1"
<|> tokenL "DC3" <|> tokenL "DC4"
<|> tokenL "SYN" <|> tokenL "ETB"
<|> tokenL "EM" <|> tokenL "SUB"
<|> tokenL "GS" <|> tokenL "RS"
<|> tokenL "SP" <|> tokenL "DEL"
) %> "ascii"

cntrl ::=

<|>
<|>
<|>
<|>
<|>
<|>
<|>
<|>
<|>
<|>

tokenL
tokenL
tokenL
tokenL
tokenL
tokenL
tokenL
tokenL
tokenL
tokenL

"STX"
"ENQ"
"BST"
"FF"
"SI"
"DC2"
"NAK"
"CAN"
"FS"
"US"

ASClarge | "@" | "[" | "\\" | "]" | "^" | "_".

April 3, 2015

Handling the offside rule

os :: [Int] -> TLPs -> TLPs


os [] []
= []
os (_:cols) []
= (("special","}"),(-1,-1)) : os cols []
os [] [((t,le),(li,c))]
= [((t, le),(li,c))]
os [] (((t, le),(li,c)):((t, le),(li,c)):tlps)
| oskeyword t le
=
((t, le),(li,c))
: (("special","{"),(li,c))
: os [c] (((t, le),(li,c+1)):tlps)
| otherwise
=
((t, le),(li,c))
: os [] (((t, le),(li,c)):tlps)
os (col:cols) [((t, le),(li,c))]
| c < col
=
(("special","}"),(li,c))
: os cols [((t, le),(li,c))]
| c == col
=
(("special",";"),(li,c))
: os (col:cols) [((t, le),(li,c+1))]
| otherwise
= ((t, le),(li,c)) : os (col:cols) []

61

os (col:cols) (((t, le),(li,c)):((t, le),(li,c)):tlps)


| c < col
=
(("special","}"),(li,c))
: os cols (((t, le),(li,c)):((t, le),(li,c))
: tlps)
| c == col
=
(("special",";"),(li,c))
: os (col:cols)
(((t, le),(li,c+1)):((t, le),(li,c)):tlps)
| oskeyword t le
=
((t, le),(li,c))
: (("special","{"),(li,c))
: os (c:col:cols) (((t, le),(li,c+1)):tlps)
| otherwise
=
((t, le),(li,c))
: os (col:cols) (((t, le),(li,c)):tlps)
oskeyword :: String -> String -> Bool
oskeyword tag lexeme
= tag == "reservedid" && (lexeme elem oskeywords)
oskeywords :: [String]
oskeywords = ["where", "let", "do", "of"]

39.6

Diagnostics

unlex tlps undoes all of the above good work by unrolling all of
the lexing of tlps. It should be very useful to check for instance that
the offside rule has been applied properly.
unlex :: TLPs -> String
unlex
= ul 0
where
ul :: Col -> TLPs -> String
ul _ []
= "\n"
ul indent (((t,le),(_,_)):tlps)
| t == "special" && le == "{"
= "{\n" ++ take (indent+3) (repeat )
++ ul (indent+3) tlps
| t == "special" && le == ";"
= "\n" ++ take (indent) (repeat ) ++ "; "
++ ul indent tlps
| t == "special" && le == "}"
= "\n" ++ take (indent-3) (repeat ) ++ "}\n"
++ ul (indent-3) tlps
| otherwise
= le ++ " " ++ ul indent tlps

39.7

Poor mans parsing

This section contains functions for analysing the results of the lexing phases above without using a real (combinator) Parser. This
method might turn out to be good enough to generate the sort of
information required to create the Haskell dictionary which started
me down this path. I have also used it for logical line counting.
Call this function BEFORE the next one. promoteMethods tlps
promotes the definitions within the where clause of class declatations
to the top level.
promoteMethods :: TLPs -> TLPs
promoteMethods =
pm 0
where
pm :: Int -> TLPs -> TLPs
-- the Int = 0 for not in class, 1 = in class,
-- 2 = class with where 3, .. deeper levels
pm _ [] =
[]
pm 0 ((("reservedid","class"),p):tlps) =
(("reservedid","class"),p) : pm 1 tlps
pm 0 (tlp:tlps) =
tlp : pm 0 tlps
pm 1 ((("special",";"),p):tlps) =
(("special",";"),p) : pm 0 tlps
pm 1 ((("special","{"),p):tlps) =
(("special",";"),p) : pm 2 tlps
pm 2 ((("special","}"),p):tlps) =
(("special",";"),p) : pm 0 tlps

April 3, 2015

pm n ((("special","{"),p):tlps) =
(("special","{"),p) : pm (n+1) tlps
pm n ((("special","}"),p):tlps) =
(("special","}"),p) : pm (n-1) tlps
pm n (tlp:tlps) =
tlp : pm n tlps
This function makes it easier to pick out top-level declarations.
discardInners tlps filters out all less-than-top-level declarations,
crudely by eliminating all {stuff; stuff; ... ; stuff} sequences inside
the top-level such sequence in tlps.
discardInners :: TLPs -> TLPs
discardInners
= di 0
where
di :: Int -> TLPs -> TLPs
di _ []
= []
di level ((("special","{"),p):tlps)
| level <= 0
= (("special","{"),p) : di 1 tlps
| otherwise
= di (level+1) tlps
di level ((("special","}"),p):tlps)
| level <= 1
= (("special","}"),p) : di 0 tlps
| otherwise
= di (level-1) tlps
di level (tlp:tlps)
| level <= 1
= tlp : di level tlps
| otherwise
= di level tlps
discard tlps discards all lexemes from a TLP list, tlps, up to but not
including the next semi-colon or opening or closing brace.
discard :: TLPs -> TLPs
discard []
= []
discard ((("special","{"),p):tlps)
= ((("special","{"),p):tlps)
discard ((("special","}"),p):tlps)
= ((("special","}"),p):tlps)
discard ((("special",";"),p):tlps)
= ((("special",";"),p):tlps)
discard (_:tlps)
= discard tlps
moduleName tlps extracts the name of the module from a TLP list,
tlps, if there is one, or returns [] if there is none.
moduleName :: TLPs -> TLPs
moduleName
((("reservedid","module"),_):(("conid",name),pos):_)
= [(("module",name),pos)]
moduleName _
= []
declarations tlps a TLP list, tlps, up into its top level declarations.
declarations :: TLPs -> [TLPs]
declarations
= (d [] 0) . (drop 1) . discard
where
d :: TLPs -> Int -> TLPs -> [TLPs]
d [] _ []
= []
d [] 0 ((("special","}"),_):_)
= []
d [] n ((("special",";"),_):tlps)
= d [] n tlps
d [] n (tlp:tlps)
= d [tlp] n tlps
d (tlp:tlps) _ []
= [reverse (tlp:tlps)]
d (tlp:tlps) n ((("special","}"),pos):tlps)
| n == 0
= [reverse ((("special","}"),pos):tlp:tlps)]
| otherwise
= d ((("special","}"),pos) : tlp : tlps)

62

(n-1) tlps
d (tlp:tlps) n ((("special","{"),pos):tlps)
= d ((("special","{"),pos) : tlp : tlps)
(n+1) tlps
d (tlp:tlps) n ((("special",";"),pos):tlps)
| n == 0
= reverse ((("special",";"),pos):tlp:tlps)
: d [] 0 tlps
| otherwise
= d ((("special",";"),pos):tlp:tlps) n tlps
d (tlp:tlps) n (tlp:tlps)
= d (tlp:tlp:tlps) n tlps
declared tlps takes a top-level declaration tlps and returns the
type of declaration (as a new set of Tags), the names of the declared
objects (Lexemes) and the positions of the names of the objects
(Poss).

newtypeD ((("conid",name),pos):_)
= [(("newtype",name),pos)]
newtypeD _
= []
classD :: TLPs -> TLPs
classD tlps
= case dropContext tlps of
[]
-> classD tlps
(tlp:tlps) -> classD (tlp:tlps)
where
classD ((("conid",name),pos):_)
= [(("class",name),pos)]
classD _
= []

declared :: TLPs -> TLPs


declared ((("reservedid","import"),_):tlps)
= importD tlps
declared ((("reservedid","infixl"),_):tlps)
= fixityD tlps
declared ((("reservedid","infixr"),_):tlps)
= fixityD tlps
declared ((("reservedid","infix"),_):tlps)
= fixityD tlps
declared ((("reservedid","type"),_):tlps)
= typeD tlps
declared ((("reservedid","data"),_):tlps)
= dataD tlps
declared ((("reservedid","newtype"),_):tlps)
= newtypeD tlps
declared ((("reservedid","class"),_):tlps)
= classD tlps
declared ((("reservedid","instance"),_):tlps)
= instanceD tlps
declared tlps
= declD tlps

instanceD :: TLPs -> TLPs


instanceD tlps
= case dropContext tlps of
[]
-> instanceD tlps
(tlp:tlps) -> instanceD (tlp:tlps)
where
instanceD
((("conid",name),pos):tlps)
= [(("instance",name ++ " "
++ instanceName tlps),pos)]
instanceD _
= error "instance case undefined"
instanceName :: TLPs -> String
instanceName (((_,"where"),_):_)
= ""
instanceName (((_,"("),_):tlps)
= ( : instanceName tlps
instanceName (((_,")"),_):tlps)
= ) : instanceName tlps
instanceName (((_,name),_):tlps)
= : name ++ instanceName tlps
instanceName _
= undefined "instanceD.instanceName"

importD
importD
importD
importD

dropContext
dropContext
dropContext
dropContext

:: TLPs -> TLPs


((("varid","qualified"),_):tlps) = importD tlps
((("conid",name),pos):_) = [(("import",name),pos)]
_ = []

fixityD :: TLPs -> TLPs


fixityD ((("integer",_),_):tlps)
= fixityD tlps
fixityD ((("special",_),_):tlps)
= fixityD tlps
fixityD ((("consym",name),pos):tlps)
= (("fixity",name),pos) : fixityD
fixityD ((("conid",name),pos):tlps)
= (("fixity",name),pos) : fixityD
fixityD ((("varid",name),pos):tlps)
= (("fixity",name),pos) : fixityD
fixityD ((("varsym",name),pos):tlps)
= (("fixity",name),pos) : fixityD
fixityD _
= []

tlps
tlps
tlps
tlps

typeD :: TLPs -> TLPs


typeD ((("conid",name),pos):_) = [(("type",name),pos)]
typeD _ = []
dataD :: TLPs -> TLPs
dataD tlps
= case dropContext tlps of
[]
-> dataD tlps
(tlp:tlps) -> dataD (tlp:tlps)
where
dataD ((("conid",name),pos):_)
= [(("data",name),pos)]
dataD _
= []
newtypeD :: TLPs -> TLPs
newtypeD tlps
= case dropContext tlps of
[]
-> newtypeD tlps
(tlp:tlps) -> newtypeD (tlp:tlps)
where

April 3, 2015

:: TLPs -> TLPs


[] = []
((("reservedop","=>"),_):tlps) = tlps
(_:tlps) = dropContext tlps

declD :: TLPs -> TLPs


declD tlps
= case decider tlps of
"::" -> typedeclD tlps
"=" -> valdeclD tlps
""
-> []
_
-> undefined "declD"
where
decider :: TLPs -> Lexeme
decider [] = []
decider ((("reservedop","::"),_):_) = "::"
decider ((("reservedop","="),_):_) = "="
decider (_:tlps) = decider tlps
typedeclD :: TLPs -> TLPs
typedeclD ((("special",_),_):tlps)
= typedeclD tlps
typedeclD ((("varid",name),pos):tlps)
= (("variable",name),pos) : typedeclD tlps
typedeclD ((("varsym",name),pos):tlps)
= (("variable",name),pos) : typedeclD tlps
typedeclD _ = []
valdeclD :: TLPs -> TLPs
valdeclD (((_,_),pos):_)
= [(("equation",""),pos)]
valdeclD _ = undefined "valdeclD"

40

Playing Cards

Module ABR.PlayingCards provides basic data types for card playing games and problems.
module ABR.PlayingCards (
Suit(..), Rank(..), suits, ranks, Card(..), Deck,

63

deck52, deck54, Hand, shuffle


) where

R7
R8
R9
R10
Jack
Queen
King

import Data.List
import System.Random

40.1

Maintenance notes

showsPrec _ c = case c of
Suit r s -> shows r . shows s
Joker
-> showString "Jkr"

Data types

Most cards are members of one of these Suit s:


Diamonds (); Hearts (); Spades ().

Clubs ();

data Suit = Clubs | Diamonds | Hearts | Spades


deriving (Eq, Ord, Enum, Bounded)
Most cards have of one of these Rank s: Ace ; R2 ; R3 ; R4 ; R5 ;
R6 ; R7 ; R8 ; R9 ; R10 ; Jack ; Queen ; King .
data Rank = Ace | R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 |
R10 | Jack | Queen | King
deriving (Eq, Ord, Enum, Bounded)
ranks is the set of all ranks. suits is the set of all suits.
ranks
ranks
suits
suits

:: [Rank]
= [minBound..maxBound]
:: [Suit]
= [minBound..maxBound]

A Card is either a card belonging to one of the Suit s (with a


rank and a suit ), or is a Joker .
data Card =

Suit {rank :: Rank,


suit :: Suit}
| Joker
deriving (Eq)

A Deck of cards, or a Hand of cards:


type Deck = [Card]
type Hand = [Card]

40.3

showChar 7
showChar 8
showChar 9
showString "10"
showChar J
showChar Q
showChar K

instance Show Card where

Requires review.

40.2

->
->
->
->
->
->
->

Creating decks

deck52 returns all the cards in a standard 52-card deck. deck54


returns all the cards in a standard 52-card deck plus 2 jokers.
deck52, deck54 :: Deck
deck52 = [Suit r s | s <- suits, r <- ranks]
deck54 = deck52 ++ [Joker, Joker]

40.5

Tests

testShuffle54 :: IO ()
testShuffle54 = do
cs <- shuffle deck54
print $ length cs
print cs

41

Poker

Module ABR.Poker provides basic stuff like categorisation of hands,


not tactics.
module ABR.Poker (
sortByRankSuit, sortBySuitRank, groupByRank,
groupBySuit, areSuccRanks, HandType(..), handType,
isGarbage, isPair, isTwoPair, isTriple, isStraight,
isFlush, isFullHouse, isPoker, isStraightFlush,
compareCards, compareGroups, compareHands, beats,
ties
) where
import Data.List
import ABR.Data.List
import ABR.PlayingCards

41.1

Maintenance notes

Requires review.

41.2

Presorting and grouping

sortByRankSuit cs sorts cs by rank and then suit.


sortBySuitRank cs sorts cs by suit and then rank.

shuffle deck returns all the cards in deck in a new random order.
shuffle :: Deck -> IO Deck
shuffle cs = s (length cs) cs []
where
s n cs ds | n == 1 = return $ cs ++ ds
| otherwise = do
i <- getStdRandom (randomR (0, n-1))
let c = cs !! i
s (n - 1) (delete c cs) (c : ds)

40.4

Instance declarations

instance Show Suit where


showsPrec _
Clubs
Diamonds
Hearts
Spades

s = case s of
-> showChar C
-> showChar D
-> showChar H
-> showChar S

instance Show Rank where


showsPrec _
Ace
->
R2
->
R3
->
R4
->
R5
->
R6
->

April 3, 2015

s = case
showChar
showChar
showChar
showChar
showChar
showChar

s of
A
2
3
4
5
6

sortByRankSuit, sortBySuitRank :: [Card] -> [Card]


sortByRankSuit = sortBy (\(Suit r s) (Suit r s) ->
case compare r r of
EQ -> compare s s
o -> o
)
sortBySuitRank = sortBy (\(Suit r s) (Suit r s) ->
case compare s s of
EQ -> compare r r
o -> o
)
groupByRank cs groups the cards in cs by common ranks. Each
group will be sorted by suit. All the groups are sorted by the length
of the group. groupBySuit cs groups the cards in cs by common
suits. Each group will be sorted by rank. All the groups are sorted
by the length of the group.
groupByRank, groupBySuit :: [Card] -> [[Card]]
groupByRank = sortByLength
. groupBy (\(Suit r _) (Suit r _) -> r == r)
. sortByRankSuit
groupBySuit = sortByLength
. groupBy (\(Suit _ s) (Suit _ s) -> s == s)
. sortBySuitRank
areSuccRanks r r0 returns True iff r0 is the next highest rank after
r.

64

41.4

areSuccRanks :: Rank -> Rank -> Bool


areSuccRanks r r = case r of
King -> case r of Ace -> True
_
-> False
_
-> succ r == r

The ordering of cards for the purpose of comparing hands is based


solely on rank. Aces have the highest value.

allSuccRanks H returns True iff all the cards in H have consecutive ranks.
allSuccRanks :: Hand -> Bool
allSuccRanks h =
let asr rs = and $ zipWith areSuccRanks rs (tail rs)
in case sort $ map rank h of
Ace : rs -> asr (Ace : rs) || asr (rs ++ [Ace])
rs
-> asr rs

41.3

Categorisation of hands

2.
3.
4.
5.

6.
7.
8.
9.

Garbage not any of the other kinds, worth only the ranks
of its cards;
Pair two cards have the same rank and all of the other
cards have different ranks;
TwoPair there are two pairs of different ranks and the other
card is yet another rank.
Triple three cards have the same rank and the rest other
different ranks;
Straight the cards all have sequential ranks (an ace can
preceed a deuce or follow a king) and some cards have different
suits;
Flush - all cards are the same suit, but not with sequential
ranks;
FullHouse a triple and a pair;
Poker four of a kind;
StraightFlush all cards have sequential ranks and the same
suit.

data HandType = Garbage | Pair | TwoPair | Triple


| Straight | Flush | FullHouse | Poker | StraightFlush
deriving (Eq, Ord, Show, Enum)
handType H classifies H.
handType :: Hand -> HandType
handType h = case groupByRank h of
[[_], [_], [_], [_,_]]
-> Pair
[[_], [_,_], [_,_]]
-> TwoPair
[[_], [_], [_,_,_]]
-> Triple
[[_,_], [_,_,_]]
-> FullHouse
[[_], [_,_,_,_]]
-> Poker
[[_], [_], [_], [_], [_]] ->
case groupBySuit h of
[h] | allSuccRanks h -> StraightFlush
| otherwise
-> Flush
_
| allSuccRanks h -> Straight
| otherwise
-> Garbage
isGarbage ,

isPair ,

isStraight ,
isStraightFlush
kind of hand.

isTwoPair ,

isFlush ,

isStraight ,

isFullHouse ,

isTriple ,

isPoker

and

each return True iff some hand is that

isGarbage, isPair, isTwoPair, isTriple, isStraight,


isFlush, isFullHouse, isPoker, isStraightFlush
:: Hand -> Bool
isGarbage
h = handType h == Garbage
isPair
h = handType h == Pair
isTwoPair
h = handType h == TwoPair
isTriple
h = handType h == Triple
isStraight
h = handType h == Straight
isFlush
h = handType h == Flush
isFullHouse
h = handType h == FullHouse
isPoker
h = handType h == Poker
isStraightFlush h = handType h == StraightFlush
isRoyalFlush
h = handType h == StraightFlush &&
(let rs = map rank h
in Ace elem rs && King elem rs)

April 3, 2015

instance Ord Card where


compare (Suit r s) (Suit
Ace -> case r of Ace
_
_
-> case r of Ace
_

r
->
->
->
->

s) = case r of
EQ
GT
LT
compare r r

compareCards cs cs 0 compares two list of cards starting by comparing the highest ranked cards.
compareCards :: [Card] -> [Card] -> Ordering
compareCards cs cs =
compare (reverse (sort cs)) (reverse (sort cs))

A HandType is one of (in order of increasing value):


1.

Comparison of hands

compareGroups css css 0 compares lists of lists of cards, considering


the corresponding lists in the order they come. Precondition: css
and css 0 are the same length.
compareGroups :: [[Card]] -> [[Card]] -> Ordering
compareGroups (cs : css) (cs:css) =
case compareCards cs cs of
EQ -> compareGroups css css
o -> o
compareGroups [] []
= EQ
compareHands H H 0 compares two hands.
compareHands :: Hand -> Hand -> Ordering
compareHands h h =
let t = handType h
t = handType h
in case compare t t of
EQ -> case t of
Garbage
-> compareCards h h
Pair
->
let [[c1], [c2], [c3], [c4,_]] = groupByRank h
[[d1], [d2], [d3], [d4,_]] = groupByRank h
in compareGroups [[c4], [c1,c2,c3]]
[[d4], [d1,d2,d3]]
TwoPair
->
let [[c1], [c2,_], [c3,_]] = groupByRank h
[[d1], [d2,_], [d3,_]] = groupByRank h
in compareGroups [[c2,c3], [c1]]
[[d2,d3], [d1]]
Triple
->
let [[_], [_], [c1,_,_]] = groupByRank h
[[_], [_], [d1,_,_]] = groupByRank h
in compare c1 d1
Straight
-> compareCards h h
Flush
-> compareCards h h
FullHouse
->
let [[c1,_], [c2,_,_]] = groupByRank h
[[d1,_], [d2,_,_]] = groupByRank h
in compare c2 d2
Poker
->
let [[_], [c1,_,_,_]] = groupByRank h
[[_], [d1,_,_,_]] = groupByRank h
in compare c1 d1
StraightFlush -> compareCards h h
o -> o
H beats H 0 iff H is a better poker hand than H 0 . H ties H 0 if
they have the same value.
beats, ties :: Hand -> Hand -> Bool
beats h h = compareHands h h == GT
ties h h = compareHands h h == EQ
better H H 0 returns the better of hands H and H 0 .
better :: Hand -> Hand -> Hand
better h h | h beats h = h
| otherwise
= h

65

41.5

Hands with more than 5 cards

best5 H picks the highest scoring 5 cards from H. Precondition


H contains at least 5 cards.
best5 :: Hand -> Hand
best5 = foldl1 better . combinations 5

41.6

Tests

-- generate a random hand h, print h and f h


test :: Show a => (Hand -> a) -> IO ()
test f = do
deck <- shuffle deck52
let hand = take 5 deck
print hand
print $ f hand
-- generate random hands h of size n, until p (f h).
-- Print h and f h
seek ::
Show a => Int -> (Hand -> a) -> (a -> Bool) -> IO ()
seek n f p = do
deck <- shuffle deck52
let hand = take n deck
if p (f hand) then do print hand
print $ f hand
else seek n f p

43

(Experimental) Data.Graph

Module ABR.Data.Graph implements a directed, unweighted graph


as an ADT. This is UNDER CONSTRUCTION. This implementation closely follows that described by Rabhi and Lapalme [4] and
Launchbury [5].
module ABR.Data.Graph (
Graph(..), SGraph, mapG, transposeG
-- isReachable, reachable, isCyclic
) where
import Data.Array
import Control.Monad.ST.Lazy
import ABR.Data.SparseSet

43.1

Maintenance notes

Reviewed 2012-11-24: Moved into ABR.Data.

43.2

Graph abstract data type

A vertex is a value from some enumerated type and for implementation reasons must usually be an instance of classes Eq, Ord, Ix and
Show.
An edge is an ordered association two vertices. Note that we are
only dealing with unweighted graphs here.
type Edge v = (v,v)

-- generate 2 random hands and compare


cmp :: IO ()
cmp = do
d <- shuffle deck52
let (h,d) = splitAt 5 d
h = take 5 d
putStrLn $ show h ++ " " ++ show h
putStrLn $ show (handType h) ++ " " ++
show (handType h)
print $ compareHands h h

A graph G = (V, E) consists of a set of edges E that connect a


set of vertices V . The set of edges E is a relation on V . If G is a
directed graph, E is not symmetric. The number of vertices is |V |
and the number of edges is |E|.
A graph, as an abstract data type is defined by the methods of
this type class.

42

vertices G returns the list of vertices V in graph G.

class Graph g where


mkGraph v v 0 E builds a graph (V, E). The set of vertices V assumed
to exist is the range [v..v 0 ]. E is the relation defining the edges.
mkGraph :: Ix v => v -> v -> [Edge v] -> g v

SendMail

Module ABR.SendMail lets a Haskell program send an email.


module ABR.SendMail (sendMail) where

vertices :: Ix v => g v -> [v]


boundsG G returns the least and greatest of the vertices V in graph
G.
boundsG :: Ix v => g v -> (v,v)

import System.Process
import System.IO
import Control.Monad

42.1

Maintenance notes

edges G returns the list of edges E in graph G.


edges :: Ix v => g v -> [Edge v]
adjacent G v returns the list of vertices in graph G that can be
reached from vertex v in one step.
adjacent :: Ix v => g v -> v -> [v]

Requires review.
Reviewed 2015-02-10. Passed hlint.

42.2

Function

sendMail whoTo subject content sends an email to whoTo about


subject containing content.
sendMail :: String -> String -> String -> IO ()
sendMail whoTo subject content = do
(inp, out, err, process) <- runInteractiveProcess
"/usr/lib/sendmail"
["-i", "-t"]
Nothing
Nothing
hPutStrLn inp $ "To: " ++ whoTo
hPutStrLn inp $ "Subject: " ++ subject
hPutStrLn inp ""
hPutStrLn inp content
hClose inp
output <- hGetContents out
unless (null output) $ putStrLn output
errors <- hGetContents err
unless (null errors) $ putStrLn errors
exit <- waitForProcess process
exit seq return ()

April 3, 2015

isAdjacent G v v 0 returns True iff vertex v 0 in graph G can be


reached from vertex v in one step.
isAdjacent :: Ix v => g v -> v -> v -> Bool

43.3

Sparse graph type

There are several ways to represent graphs as a Haskell data structure. The following is likely to be suitable when the graph is sparse.
newtype SGraph vertex =
SGraph (Array vertex (SparseSet vertex))
deriving (Show)
This uses an array to map each vertex v in V to the set of vertices
reachable from v in one step.
instance Graph SGraph where
mkGraph v v vvs = SGraph $
accumArray (flip insertSS) emptySS (v,v) vvs
vertices (SGraph g) = indices g
boundsG (SGraph g) = bounds g
edges (SGraph g) =
[(v,v) | v <- indices g, v <- flattenSS (g!v)]
adjacent (SGraph g) v = flattenSS (g ! v)
isAdjacent (SGraph g) v v = v elemSS (g ! v)

66

43.4

Example sparse graphs

ga is a non-cyclic graph.
ga :: SGraph Int
ga = mkGraph 0 9 [
(0,1), (0,3),
(1, 2),
(3, 4), (3,5), (3, 6), (3,1)
]
gb is a cyclic graph.
gb :: SGraph Int
gb = mkGraph 0 9 [
(0,1), (0,3),
(1, 2), (1,0),
(3, 4), (3,5), (3, 6), (3,1)
]

43.5

Graph Operations

mapG f v v 0 G builds a new graph G0 formed by applying f to every


edge in G, such that:
1. G = (V, E)
2. G0 = (V 0 , E 0 )
3. V 0 = [v..v 0 ]
4. E 0 = {f e|e E}

43.7

Cycles detection

isCyclic g returns True iff graph g is cyclic. A depth-first search


is used.
isCyclic :: (Ix v, Enum v) => Graph v -> Bool
isCyclic g
= let (lo,hi) = bounds g
in or [isReachable g v v | v <- [lo..hi]]

43.8

Dense graph type

When the graph is dense a representation using an array of arrays


may be more suitable.

43.9

Imported Stuff

mapT :: (Vertex -> a -> b) -> Table a -> Table b


mapT f t = array (Array.bounds t) [(v, f v (t ! v)) | v <- Array.in
type Bounds = (Vertex, Vertex)
outdegree :: Graph -> Table Int
outdegree g = mapT numEdges g
where
numEdges v ws = length ws

mapG :: (Ix v, Ix v, Graph g, Graph g) =>


(Edge v -> Edge v) -> v -> v -> g v -> g v
mapG f v v = mkGraph v v . map f . edges

buildG :: Bounds -> [Edge] -> Graph


buildG bnds es = accumArray (flip (:)) [] bnds es

transposeG G reverses all the edges in G.

transposeG :: Graph -> Graph


transposeG g = buildG (Array.bounds g) (reverseE g)

transposeG :: (Ix v, Graph g) => g v -> g v


transposeG g =
let (v,v) = boundsG g
in mapG (\(v,v) -> (v,v)) v v g

43.6

Reachability

isReachable g v v returns True iff a vertex v in graph g is reachable from vertex v. A depth-first search is used. This implementation uses a mutable array so that already visited nodes can be
skipped in constant time.
isReachable :: (Ix v) => Graph v -> v -> v -> Bool
isReachable g v v = runST (do
visited <- newSTArray (bounds g) False
let ir toVisit = case toVisit of
[]
-> return False
(v:vs) -> if v == v
then return True
else do
vv <- readSTArray visited v
if vv
then ir vs
else do
writeSTArray visited v True
ir (adjacent g v ++ vs)
ir (adjacent g v)
)
reachable g v returns the list of vertices in graph g that are
reachable from vertex v. A depth-first search is used. This implementation uses a mutable array so that already visited nodes can
be skipped in constant time.
reachable :: (Ix v) => Graph v -> v -> [v]
reachable g v = runST (do
visited <- newSTArray (bounds g) False
let r toVisit rvs = case toVisit of
[]
-> return rvs
(v:vs) -> do
vv <- readSTArray visited v
if vv
then r vs rvs
else do
writeSTArray visited v True
r (adjacent g v ++ vs) (v : rvs)
r (adjacent g v) []
)

April 3, 2015

reverseE :: Graph -> [Edge]


reverseE g = [ (w,v) | (v,w) <- edges g]
indegree :: Graph -> Table Int
indegree g = outdegree (transposeG g)
---------------------------------------------------------- TREE
--------------------------------------------------------data Tree a = Node a (Forest a) deriving Show
type Forest a = [Tree a]
---------------------------------------------------------- Depth First Numbering
--------------------------------------------------------preorder :: Tree a -> [a]
preorder (Node a ts) = [a] ++ preorderF ts
preorderF :: Forest a -> [a]
preorderF ts = concat (map preorder ts)
preOrd :: Graph -> [Vertex]
preOrd g = preorderF (dff g)
tabulate :: Bounds -> [Vertex] -> Table Int
tabulate bnds vs = array bnds (zip vs [1..])
preArr :: Bounds -> Forest Vertex -> Table Int
preArr bnds ts = tabulate bnds (preorderF ts)
---------------------------------------------------------- Topological Sorting
--------------------------------------------------------postorder :: Tree a -> [a]
postorder (Node a ts) = postorderF ts ++ [a]
postorderF :: Forest a -> [a]
postorderF ts = concat (map postorder ts)
postOrd :: Graph -> [Vertex]
postOrd g = postorderF (dff g)
topSort :: Graph -> [Vertex]
topSort g = reverse (postOrd g)

67

select v ws = [ w | w <- ws, post!v > post!w, pre!v


postArr bnds ts = tabulate bnds (postorderF ts)
---------------------------------------------------------- Connected Components
---------------------------------------------------------

forward :: Graph -> Graph -> Table Int -> Graph


forward g tree pre = mapT select g
where
select v ws = [ w | w <- ws, pre!v < pre!w, w notE
-select v ws = [ w | w <- ws, pre!v < pre!w] \\ tr

components :: Graph -> Forest Vertex


components g = dff (undirected g)

---------------------------------------------------------- All Paths


undirected :: Graph -> Graph
--------------------------------------------------------undirected g = buildG (Array.bounds g) (edges g ++ reverseE g)
type Path = [Vertex]
---------------------------------------------------------- allpaths :: Graph -> Vertex -> Vertex -> Path
-- Strongly connected components
------------------------------------------------------------------------------------------------------------------ Main Routine
scc :: Graph -> Forest Vertex
--------------------------------------------------------scc g = dfs (transposeG g) (reverse (postOrd g))
makeFromTo h l = hIsEOF h
>>= \b ->
scc :: Graph -> Forest Vertex
if b then return l else
scc g = dfs g (reverse (postOrd (transposeG g)))
-- hGetLine h
hGetChar h
--------------------------------------------------------hGetChar h
-- Depth First Search Algorithms
hGetChar h
--------------------------------------------------------consumetherest h
>>
-hGetChar h
dfs :: Graph -> [Vertex] -> Forest Vertex
makeFromTo h (l ++ [(f,t)])
dfs g vs = prune (Array.bounds g) (map (generate g) vs)
where
consumetherest h = hIsEOF h >>= \b ->
dff :: Graph -> Forest Vertex
if b then return []
dff g = dfs g (vertices g)
else hGetChar h >>= \t ->
if t == \n then return [] else co
generate :: Graph -> Vertex -> Tree Vertex
generate g v = Node v (map (generate g) (g ! v))
readGraph f = if f == "" then makeFromTo stdin []
else openFile f ReadMode
>>= \h -> makeFromT
type Set s = STArray s Vertex Bool
execute "-pre-order" f = readGraph f >>= \((f,t):xs) ->
mkEmpty :: Bounds -> ST s (Set s)
let g = buildG (f,t) xs in print (preArr (f,t) (dfs
mkEmpty bnds = newArray bnds False
execute "-post-order" f = readGraph f >>= \((f,t):xs) ->
let g = buildG (f,t) xs in print (postArr (f,t) (df
contains :: Set s -> Vertex -> ST s Bool
execute "-scc" f = readGraph f >>= \((f,t):xs) ->
contains m v = readArray m v
let g = buildG (f,t) xs in print (scc g)
execute "-forward-edge" f = readGraph f >>= \((f,t):xs) ->
include :: Set s -> Vertex -> ST s ()
let
include m v = writeArray m v True
g
= buildG (f,t) xs
dfstree = dfs g [f..t]
prune :: Bounds -> Forest Vertex -> Forest Vertex
in
prune bnds ts = runST (mkEmpty bnds >>= \m -> chop m ts)
print (forward g (tree (f,t) dfstree) (preA
execute "-tree-edge" f = readGraph f >>= \((f,t):xs) ->
chop :: Set s -> Forest Vertex -> ST s (Forest Vertex)
let g = buildG (f,t) xs
chop m [] = return []
in
chop m (Node v ts : us) = contains m v >>= \visited ->
print (tree (f,t) (dfs g [f..t]))
if visited then
execute "-back-edge" f = readGraph f >>= \((f,t):xs) ->
chop m us
let g = buildG (f,t) xs
else
in print (back g (postArr (f,t) (dfs g [f..t])))
include m v
>>
execute "-cross-edge" f = readGraph f >>= \((f,t):xs) ->
chop m ts
>>= \as ->
let
chop m us
>>= \bs ->
g
= buildG (f,t) xs
return ((Node v as) : bs)
dfstree = dfs g [f..t]
in print (cross g (preArr (f,t) dfstree) (postArr (
---------------------------------------------------------- Classification of graph edges
execute a _ = putStr "I dont know the option, "
>>
--------------------------------------------------------putStr a
putStr "\n"
tree :: Bounds -> Forest Vertex -> Graph
printUsage
tree bnds ts = buildG bnds (concat (map flat ts))
where
printUsage = getProgName
>>= \pname ->
flat (Node v ts) = [ (v,w) | Node w us <- ts ] ++ concat (map putStr
flat ts)
pname
>>
putStr " ["
>>
back :: Graph -> Table Int -> Graph
putStr "-pre-order"
>>
back g post = mapT select g
putStr "| -post-order"
>>
where
putStr "| -scc"
>>
select v ws = [ w | w <- ws, post!v < post!w ]
putStr "| -forward-edge" >>
putStr "| -back-edge" >>
cross :: Graph -> Table Int -> Table Int -> Graph
putStr "| -tree-edge" >>
cross g pre post = mapT select g
putStr "| -cross-edge" >>
where
putStr "| -all-cycles" >>

April 3, 2015

68

putStr "] "


putStr "file"
putStr "\n"
main = getArgs >>= \args ->
case args of
[]
(a:[])
(a:f:_)

>>
>>

Connections

A MYSQL is some opaque C object. A pointer to this structure is


our handle on a connection.
type MYSQL =
()

-> printUsage
-> execute a ""
-> execute a f

ex1 = buildG (a, j) [


(a, j),
(a, g),
(b, i),
(b, a),
(c, h),
(c, e),
(e, j),
(e, h),
(e, d),
(f, i),
(g, f),
(g, b)]

44

44.2.2

(Experimental) MySQL C API


Binding

44.2.3

Results

A MYSQL RES is some opaque C object. A pointer to this structure


is our handle on a result to a query.
type MYSQL_RES =
()

44.2.4

Rows

A MYSQL ROW is an array of strings. The strings are not terminated


with \0s, as they could be binary data.
type MYSQL_ROW = Ptr CString
A MYSQL ROW OFFSET is a pointer to a MYSQL ROWS.
type MYSQL_ROW_OFFSET =
Ptr ()

44.2.5

Fields

A MYSQL FIELD is a C structure.


Module ABR.MySQLCBinding is an interface to the MySQL C API.
It uses C types for all arguments and results, without any attempt
to make if Haskell friendly. Module MySQL, which is built on top of
this module, provides an interface using Haskell types.
The descriptions have been adapted from the MySQL Reference
Manual, omitting much detail, and introducing new errors. Id have
that close at hand while using this module.
module ABR.MySQLCBinding (
My_bool, My_ulonglong, MYSQL, MYSQL_RES,
MYSQL_ROW, MYSQL_ROW_OFFSET, MYSQL_FIELD,
MYSQL_FIELD_OFFSET, Enum_mysql_option,
mysql_affected_rows, mysql_change_user,
mysql_character_set_name, mysql_close,
mysql_data_seek, mysql_errno, mysql_error,
mysql_fetch_field, mysql_fetch_fields,
mysql_fetch_field_direct, mysql_fetch_lengths,
mysql_fetch_row, mysql_field_count,
mysql_field_seek, mysql_field_tell,
mysql_free_result, mysql_get_client_info,
mysql_get_host_info, mysql_get_proto_info,
mysql_get_server_info, mysql_info, mysql_init,
mysql_insert_id, mysql_kill, mysql_list_dbs,
mysql_list_fields, mysql_list_processes,
mysql_list_tables, mysql_num_fields,
mysql_num_rows, mysql_options, mysql_ping,
mysql_query, mysql_real_connect,
mysql_real_escape_string, mysql_real_query,
mysql_row_seek, mysql_row_tell, mysql_select_db,
mysql_shutdown, mysql_stat, mysql_store_result,
mysql_thread_id, mysql_use_result
) where
import Foreign
import Foreign.C

44.1

Maintenance notes

Reviewed 2012-11-24: Moved into ABR.Database.

44.2
44.2.1

API Data types


Basics

A My bool is a C char.
type My_bool = CChar
A My ulonglong is supposedly a 64 bit, unsigned integer, but the
way is used implies signed is a more useful choice.
type My_ulonglong = CLLong

April 3, 2015

type MYSQL_FIELD =
()
A MYSQL FIELD OFFSET is an offset into a MySQL field list.
type MYSQL_FIELD_OFFSET = CUInt

44.2.6

Options

An Enum mysql option


mysql options.

is a C enumeration, used by function

type Enum_mysql_option = CUInt

44.3

API Functions

mysql affected rows mysql returns the number of rows changed


by the last UPDATE, deleted by the last DELETE or inserted by the last
INSERT statement. May be called immediately after mysql query
for UPDATE, DELETE, or INSERT statements. For SELECT statements,
mysql affected rows works like mysql num rows. Returns an integer greater than zero to indicate the number of rows affected or retrieved. Zero indicates that no records where updated for an UPDATE
statement, no rows matched the WHERE clause in the query or that
no query has yet been executed. 1 indicates that the query returned an error or that, for a SELECT query, mysql affected rows
was called prior to calling mysql store result.
foreign import ccall "mysql.h"
mysql_affected_rows :: Ptr MYSQL -> IO My_ulonglong
mysql change user mysql user passwd db changes the user to
user with passwd and causes the database specified by db to become the default (current) database on the connection specified by
mysql. In subsequent queries, this database is the default for table
references that do not include an explicit database specifier. Returns zero for success, non-zero if an error occurred.
foreign import ccall "mysql.h"
mysql_change_user :: Ptr MYSQL -> CString -> CString
-> CString -> IO My_bool
mysql character set name mysql returns the default character set
for the current connection.
foreign import ccall "mysql.h"
mysql_character_set_name :: Ptr MYSQL -> IO CString
mysql close mysql closes and deallocates the connection mysql.
foreign import ccall "mysql.h"
mysql_close :: Ptr MYSQL -> IO ()

69

mysql data seek result offset seeks to an arbitrary row in a


query result set.
This requires that the result set structure
contains the entire result of the query, so mysql data seek may
be used in conjunction only with mysql store result, not with
mysql use result. The offset should be a value in the range from 0
to mysql num rows result 1.
foreign import ccall "mysql.h"
mysql_data_seek :: Ptr MYSQL_RES -> CULLong -> IO ()
mysql errno mysql returns the error code returned by the last
MySQL API function. 0 indicates no error.
foreign import ccall "mysql.h"
mysql_errno :: Ptr MYSQL -> IO CUInt
mysql error mysql returns the error message returned by the last
MySQL API function. An empty string indicates no error.
foreign import ccall "mysql.h"
mysql_error :: Ptr MYSQL -> IO CString
mysql fetch field result returns the definition of one column of a
result set as a MYSQL FIELD structure. Call this function repeatedly
to retrieve information about all columns in the result set. Returns
NULL when no more fields are left. mysql fetch field is reset to
return information about the first field each time you execute a
new SELECT query. The field returned is also affected by calls to
mysql field seek.
foreign import ccall "mysql.h"
mysql_fetch_field :: Ptr MYSQL_RES
-> IO (Ptr MYSQL_FIELD)
mysql fetch fields result returns an array of all MYSQL FIELD
structures for a result set. Each structure provides the field definition for one column of the result set.
foreign import ccall "mysql.h"
mysql_fetch_fields :: Ptr MYSQL_RES
-> IO (Ptr MYSQL_FIELD)
mysql fetch field direct result fieldnr returns column fieldnr s
field definition as a MYSQL FIELD structure. The value of fieldnr
should be in the range from 0 to mysql num fields result 1.
foreign import ccall "mysql.h"
mysql_fetch_field_direct :: Ptr MYSQL_RES -> CUInt
-> IO (Ptr MYSQL_FIELD)
mysql fetch lengths result returns an array of the lengths of the
columns of the current row within a result set, or NULL in the case
of an error.
foreign import ccall "mysql.h"
mysql_fetch_lengths :: Ptr MYSQL_RES -> IO (Ptr CULong)
mysql fetch row result retrieves the next row of a result set. Returns NULL when there are no more rows to retrieve.
foreign import ccall "mysql.h"
mysql_fetch_row :: Ptr MYSQL_RES -> IO (MYSQL_ROW)
mysql field count mysql returns the number of columns for the
most recent query on the connection.
foreign import ccall "mysql.h"
mysql_field_count :: Ptr MYSQL -> IO CUInt
mysql field seek result offset sets the field cursor to the given
offset. The next call to mysql fetch field will retrieve the field
definition of the column associated with that offset. To seek to the
beginning of a row, pass an offset value of zero. Returns the previous
value of the field cursor.
foreign import ccall "mysql.h"
mysql_field_seek :: Ptr MYSQL_RES
-> MYSQL_FIELD_OFFSET -> IO MYSQL_FIELD_OFFSET
mysql field tell result returns the position of the field cursor
used for the last mysql fetch field. This value can be used as an
argument to mysql field seek.
foreign import ccall "mysql.h"
mysql_field_tell :: Ptr MYSQL_RES
-> IO MYSQL_FIELD_OFFSET

April 3, 2015

mysql free result result frees the memory allocated for a result
set by mysql store result, mysql use result, mysql list dbs, etc.
When you are done with a result set, you must free the memory it
uses by calling this function.
foreign import ccall "mysql.h"
mysql_free_result :: Ptr MYSQL_RES -> IO ()
mysql get client info returns a string that represents the client
library version.
foreign import ccall "mysql.h"
mysql_get_client_info :: IO CString
mysql get host info mysql returns a string describing the type of
connection in use, including the server host name.
foreign import ccall "mysql.h"
mysql_get_host_info :: Ptr MYSQL -> IO CString
mysql get proto info mysql returns the protocol version used by
current connection.
foreign import ccall "mysql.h"
mysql_get_proto_info :: Ptr MYSQL -> IO CUInt
mysql get server info mysql returns a string describing the type
of connection in use, including the server host name.
foreign import ccall "mysql.h"
mysql_get_server_info :: Ptr MYSQL -> IO CString
mysql info mysql retrieves a string providing information about
the most recently executed query, but only for some statements. For
other statements, mysql info returns NULL.
foreign import ccall "mysql.h"
mysql_info :: Ptr MYSQL -> IO CString
mysql init mysql allocates or initializes a MYSQL object suitable for mysql real connect. If mysql is a null pointer (use
Foreign.nullPtr), the function allocates, initializes and returns a
new object. Otherwise the object is initialized and the address returned.
foreign import ccall "mysql.h"
mysql_init :: Ptr MYSQL -> IO (Ptr MYSQL)
mysql insert id mysql returns the ID generated for an
AUTO INCREMENT column by the previous query. Use this function
after you have performed an INSERT query into a table that contains
an AUTO INCREMENT field.
foreign import ccall "mysql.h"
mysql_insert_id :: Ptr MYSQL -> IO My_ulonglong
mysql kill mysql pid asks the server to kill the thread specified
by pid.
foreign import ccall "mysql.h"
mysql_kill :: Ptr MYSQL -> CULong -> IO CInt
mysql list dbs mysql wild returns a result set consisting of
database names on the server that match the simple regular expression specified by the wild parameter. wild may contain the
wild-card characters % or _, or may be a NULL pointer to match
all databases. Returns NULL if an error occurred.
foreign import ccall "mysql.h"
mysql_list_dbs :: Ptr MYSQL -> CString
-> IO (Ptr MYSQL_RES)
mysql list fields mysql table wild returns a result set consisting
of field names in the given table that match the simple regular
expression specified by the wild parameter. wild may contain the
wild-card characters % or _, or may be a NULL pointer to match
all fields. Returns NULL if an error occurred.
foreign import ccall "mysql.h"
mysql_list_fields :: Ptr MYSQL -> CString -> CString
-> IO (Ptr MYSQL_RES)
mysql list processes mysql returns a result set describing the
current server threads. Returns NULL if an error occurred.
foreign import ccall "mysql.h"
mysql_list_processes :: Ptr MYSQL -> IO (Ptr MYSQL_RES)

70

mysql list tables mysql wild returns a result set consisting of


table names in the current database that match the simple regular
expression specified by the wild parameter. wild may contain the
wild-card characters % or _, or may be a NULL pointer to match
all databases. Returns NULL if an error occurred.
foreign import ccall "mysql.h"
mysql_list_tables :: Ptr MYSQL -> CString
-> IO (Ptr MYSQL_RES)
mysql num fields result returns the number of columns in a result
set.
foreign import ccall "mysql.h"
mysql_num_fields :: Ptr MYSQL_RES -> IO CUInt
mysql num rows result returns the number of rows in the result set.
foreign import ccall "mysql.h"
mysql_num_rows :: Ptr MYSQL_RES -> IO My_ulonglong
mysql options mysql option arg Can be used to set extra connect
options and affect behavior for a connection.
foreign import ccall "mysql.h"
mysql_options :: Ptr MYSQL -> Enum_mysql_option
-> CString -> IO CInt
mysql ping mysql checks whether or not the connection to the
server is working. If it has gone down, an automatic reconnection
is attempted. This function can be used by clients that remain idle
for a long while, to check whether or not the server has closed the
connection and reconnect if necessary. Returns zero if the server is
alive, non-zero otherwise.
foreign import ccall "mysql.h"
mysql_ping :: Ptr MYSQL -> IO CInt
mysql query mysql query executes the SQL query pointed to by
the null-terminated string query. The query must consist of a single
SQL statement. You should not add a terminating semicolon (;)
or \g to the statement. mysql query cannot be used for queries
that contain binary data; you should use mysql real query instead.
(Binary data may contain the \0 character.)
foreign import ccall "mysql.h"
mysql_query :: Ptr MYSQL -> CString -> IO CInt
mysql real connect mysql host user passwd db port
unix socket client flag attempts to establish a connection to a
MySQL database engine running on host. mysql real connect must
complete successfully before you can execute nearly all of the other
API functions. The function returns mysql if successful, otherwise
null. The parameters are specified as follows:
mysql is a pointer to an existing MYSQL structure, initialized by
mysql init.
host may be either a hostname or an IP address.
"localhost" the local host is assumed.

If null or

foreign import ccall "mysql.h"


mysql_real_escape_string :: Ptr MYSQL -> CString
-> CString -> CUInt -> IO (CInt)
mysql real query mysql query length executes the SQL query
pointed to by query, which should be a string length bytes long.
The query must consist of a single SQL statement. You should not
add a terminating semicolon (;) or \g to the statement. You must
use mysql real query rather than mysql query for queries that contain binary data, because binary data may contain the \0 character. In addition, mysql real query is faster than mysql query
because it does not call strlen() on the query string. If you want
to know if the query should return a result set or not, you can use
mysql field count to check for this. Returns zero if the query was
successful, or non-zero if an error occurred.
foreign import ccall "mysql.h"
mysql_real_query :: Ptr MYSQL -> CString -> CUInt
-> IO (CInt)
mysql row seek result offset sets the row cursor to an arbitrary
row in a query result set. This requires that the result set structure contains the entire result of the query, so mysql row seek may
be used in conjunction only with mysql store result, not with
mysql use result. The offset should be a value returned from a
call to mysql row tell or to mysql row seek. This value is not simply a row number; if you want to seek to a row within a result
set using a row number, use mysql data seek instead. Returns the
previous value of the row cursor. This value may be passed to a
subsequent call to mysql row seek.
foreign import ccall "mysql.h"
mysql_row_seek :: Ptr MYSQL_RES -> MYSQL_ROW_OFFSET
-> IO MYSQL_ROW_OFFSET
mysql row tell result returns the current position of the row cursor for the last mysql fetch row. This value can be used as an
argument to mysql row seek. You should use mysql row tell only
after mysql store result, not after mysql use result.
foreign import ccall "mysql.h"
mysql_row_tell :: Ptr MYSQL_RES -> IO MYSQL_ROW_OFFSET
mysql select db mysql db causes the database specified by db to
become the default (current) database on the connection specified
by mysql. Returns zero for success or non-zero otherwise.
foreign import ccall "mysql.h"
mysql_select_db :: Ptr MYSQL -> CString -> IO CInt
mysql shutdown mysql level asks the database server to shut down.
The connected user must have shutdown privileges.
foreign import ccall "mysql.h"
mysql_shutdown :: Ptr MYSQL -> CInt -> IO CInt
mysql stat mysql returns a character string containing information similar to that provided by the mysqladmin status command.
This includes uptime in seconds and the number of running threads,
questions, reloads, and open tables, or NULL if an error occurred.

user is the MySQL login ID. If null, the current user is assumed.

foreign import ccall "mysql.h"


mysql_stat :: Ptr MYSQL -> IO CString

passwd is the password for user (unencrypted). If null, only users


that have empty passwords are checked.

mysql store result mysql reads and returns a pointer to the entire result for the last query, or NULL is there has been an error.

db is the database name. If not null, the connection will set the
default database to this value.

foreign import ccall "mysql.h"


mysql_store_result :: Ptr MYSQL
-> IO (Ptr MYSQL_RES)

port If not 0, sets the port number to use.


unix socket If not null, sets the socket or named pipe to use.
client flag usually 0, but can be used to cope with some special
circumstances.
foreign import ccall "mysql.h"
mysql_real_connect :: Ptr MYSQL -> CString -> CString
-> CString -> CString -> CUInt -> CString -> CUInt
-> IO (Ptr MYSQL)
mysql real escape string mysql to from length creates a legal
SQL string that you can use in a SQL statement. The string in
from is encoded to an escaped SQL string, taking into account the
current character set of the connection. The result is placed in to
and a terminating null byte is appended. Characters encoded are
NUL (ASCII 0), \n, \r, \, , , and Control-Z.

April 3, 2015

mysql thread id mysql returns the thread ID of the current connection. This value can be used as an argument to mysql kill to
kill the thread. If the connection is lost and you reconnect with
mysql ping, the thread ID will change. This means you should not
get the thread ID and store it for later. You should get it when you
need it.
foreign import ccall "mysql.h"
mysql_thread_id :: Ptr MYSQL -> IO CULong
mysql use result mysql initiates a result set retrieval but does not
actually read the result set into the client like mysql store result
does. Instead, each row must be retrieved individually by making
calls to mysql fetch row.
foreign import ccall "mysql.h"
mysql_use_result :: Ptr MYSQL -> IO (Ptr MYSQL_RES)

71

45

(Experimental)
Haskell API

MySQL

Module ABR.MySQL is a Haskell interface to MySQL. This interface


presents only Haskell data types, and restricts or hides many options
provided by the C API.
module ABR.MySQL(
MySQL, myConnect, myClose, myQuery, myFetch
) where
import Foreign
import Foreign.C
import Control.Monad
import ABR.MySQLCBinding
import ABR.Check
import ABR.DeepSeq

45.1

Maintenance notes

Reviewed 2012-11-24: Moved into ABR.Database.

45.2

Data types

45.2.1

Connections

45.3.3

Issuing a query

myQuery mysql query executes the SQL query, returning


CheckPass (fields, rows) if successful, where fields is the number of
fields that would be in any result set fetched after this query and
rows is the number of rows affected by this query or 1 if there is
a result to be fetched, or CheckFail (errNum, errMsg) if not.
myQuery :: MySQL -> String
-> IO (CheckResult (Int,Integer) (Integer, String))
myQuery mysql query = do
i <- withCStringLen query (\(query,len) ->
mysql_real_query mysql query (fromIntegral len)
)
if i == 0
then do
rows <- mysql_affected_rows mysql
fields <- mysql_field_count mysql
return (CheckPass (fromIntegral fields,
toInteger rows))
else do
e <- mysql_errno mysql
m <- mysql_error mysql
let e = toInteger e
m <- peekCString m
(e,m) deepSeq mysql_close mysql
return (CheckFail (e,m))

45.3.4

Fetching query results

A MySQL is our handle on a MySQL connection.


type MySQL =
Ptr MYSQL

45.3
45.3.1

Functions
Establishing a connection

myConnect host user passwd db returns CheckPass mysql if a connection could be established to the MySQL server running on host
("" = "localhost"), as user ("" = the current user), with password passwd ("" = no password), using database db ("" = no
database selected). If an error occurs, CheckFail (errNum, errMsg)
is returned.
myConnect :: String -> String -> String -> String
-> IO (CheckResult MySQL (Integer, String))
myConnect host user passwd db = do
mysql <- mysql_init nullPtr
mysql <- withCStringOrNull host (\host ->
withCStringOrNull user (\user ->
withCStringOrNull passwd (\passwd ->
withCStringOrNull db (\db ->
mysql_real_connect mysql host user
passwd db 0 nullPtr 0
)
)
)
)
if mysql == nullPtr
then do
e <- mysql_errno mysql
m <- mysql_error mysql
let e = toInteger e
m <- peekCString m
(e,m) deepSeq mysql_close mysql
return (CheckFail (e,m))
else return (CheckPass mysql)

45.3.2

Closing a connection

myClose mysql closes the connection and frees the memory it uses.
myClose :: MySQL -> IO ()
myClose = mysql_close

April 3, 2015

myFetch mysql fields fetches the results set for the last query. fields
is the number of fields that wil be returned, as reported by the last
call to myQuery. It returns CheckPass (rows, lss, csss), where rows
is the number of rows in the data set, lss is the list of lengths of each
field for each row, and csss is the list of rows of columns. In the
case of an error, CheckFail (errNum, errMsg) is returned instead.
myFetch :: MySQL -> Int
-> IO (CheckResult (Integer, [[Int]],
[[String]]) (Integer, String))
myFetch mysql fields = do
results <- mysql_store_result mysql
if results /= nullPtr
then do
rows <- mysql_num_rows results
let rows = toInteger rows
(lss,csss) <- getRows results rows fields
(lss,csss) deepSeq mysql_free_result results
return (CheckPass (rows, lss, csss))
else do
e <- mysql_errno mysql
m <- mysql_error mysql
let e = toInteger e
m <- peekCString m
return (CheckFail (e,m))
where
getRows :: Ptr MYSQL_RES -> Integer -> Int
-> IO ([[Int]], [[String]])
getRows results rows fields = gr rows
where
gr :: Integer -> IO ([[Int]], [[String]])
gr r | r <= 0 =
return ([], [])
| otherwise = do
row <- mysql_fetch_row results
ls <- mysql_fetch_lengths results
ls <- peekArray fields ls
let ls = map fromIntegral ls
row <- peekArray fields row
css <- mapM (\(l,p) -> do
cs <- peekArray l p
return $ map (toEnum . fromIntegral) cs
) (zip ls row)
(lss, csss) <- gr (r - 1)
return (ls : lss, css : csss)
This has not been tested the case of NULL field values, where the
row contains a null pointer.

72

45.4

46.3

Utilities

withCStringOrNull cs f extends CString.withCString by passing


a null pointer to f if cs is empty, instead of making an empty C
string.
withCStringOrNull :: String -> (CString -> IO a) -> IO a
withCStringOrNull cs f = if null cs
then f nullPtr
else withCString cs f

46
Module
TION.

(Experimental)
Database.Relational
ABR.Database.Relational

is UNDER CONSTRUC-

module ABR.Database.Relational where


import Data.List
import Data.Maybe
import ABR.Data.List

46.1

Maintenance notes

Reviewed 2012-11-24: Moved to ABR.Database.Relational from


ABR.RelationalDB

46.2

Definitions and data types

A database is a collection of tables. Each table represents a relation.


Each row of a table represents one tuple, an element of the relation.
Each column of a table, is an attribute of that table. The terms
table and relation are synonymous, as are row and tuple.
Any given database, populated with data, is an instance of a
database schema, a specification of the names and types of data in
each table attribute.
A name is just a string. Names are used to identify particular
attributes tables and databases.
type Name = String
An attribute specification consists of a name and a type.
data Attribute =

AString Name
| AInteger Name
| ADouble Name
deriving (Eq, Ord, Show)

A schema consists of a name, which may be empty, and a list of


specifications. A table schema contains a list of attribute specifications, and specifies the information to be stored in each column
of a table. The relation schema that relation r is an instance of is
denoted (r). A database schema contains a list of table schemas.

46.3.1

Relational algebra
Projection of a tuple on a relation schema

The projection t[X] of a tuple t on a relation schema X is computed


by discarding the attributes in t that do not appear in X. This
projection operation will be applied to many rows and is worth
computing just once. A projector is a function that performs a
projection on one tuple.
type Projector = Row -> Row
A projector can be computed from the new and old table schemas.
makeProjector Y X returns either:
Nothing, if the new schema Y contains an attribute not in the
old schema X; or
Just (Y 0 , p), where table schema Y 0 is the same as the requested new table schema Y with the exception that the attributes are in the same order as in the old table schema X,
and p is the projector that can be applied to tuple that conforms to X to produce a tuple the conforms to Y 0 .
makeProjector :: TableSchema -> TableSchema
-> Maybe (TableSchema, Projector)
makeProjector (Schema _ ys) (Schema _ xs) =
let ys = filter (elem ys) xs
ks = map (elem ys) xs
p = \ds -> [d | (k,d) <- zip ks ds, k]
in if null (ys \\ xs)
then Just (Schema "" ys, p)
else Nothing

46.3.2

Projection of a relation on a relation


schema

The projection X (r) of a relation r on a relation schema X, is


defined by:
1. X (r) and (X (r)) = X
2. X (r) = {t[X] : t r}
projectTable (X, p) r returns X (r) using p to project all the
tuples in r.
projectTable :: (TableSchema, Projector) -> Table -> Table
projectTable (x, p) (Table _ rows) = Table x (map p rows)
proj X r returns X (r). If the schema restrictions are not met then
the program will terminate with an error.
proj :: TableSchema -> Table -> Table
proj x r@(Table x _) = case makeProjector x x of
Nothing -> error "Can not perform projection, \
\incompatible schemas."
Just xp -> projectTable xp r

46.3.3

Natural join

The natural join r1 1 r2 of relations r1 and r2 is defined by:

data Schema a = Schema Name [a]

1. (r1 1 r2 ) = (r1 ) (r2 )

type TableSchema = Schema Attribute

2. r1 1 r2 = {t (r1 r2 )[(r1 1 r2 )] : t[(r1 )] r1 t[(r2 )]


r2 }

type DatabaseSchema = Schema TableSchema


Each element of a row is one datum. The following union type allows
each datum to be of one of a range of types. The types correspond
to the possible types of attributes, with one extra to represent the
absence of a datum.

Each tuple of the joined relation contains the combined attributes


from two tuples, one from each of the original relations. A joined
tuple is only formed if the overlapping attributes were equal.
A joiner is a function that joins two tuples if the overlapping
attributes are equal.

data Datum =

type Joiner = Row -> Row -> Maybe Row

DNull
| DString String
| DInteger Integer
| DDouble Double
deriving (Eq, Ord, Show)

Within each row, the order of the data is significant.


type Row = [Datum]
Each table has a schema that identifies the contents of each attribute.
data Table = Table TableSchema [Row]

April 3, 2015

makeJoiner X X 0 returns (X X 0 ,j), where j is a joiner that joins a


tuple that conforms to X to a tuple that conforms to X 0 , producing
a tuple conforming to X X 0 if the overlapping attributes are equal.
makeJoiner :: TableSchema -> TableSchema
-> (TableSchema, Joiner)
makeJoiner (Schema _ xs) (Schema _ xs) =
let xsxs = xs ++ xs
ixs = nubBy (\ (_,x) (_,x) -> x == x) $
zip [0..] xsxs
is = map fst ixs
ks = map (elem is) [0..length xsxs-1]

73

xs = map snd ixs


js = [(i,i) | (i,x) <- zip [0..] xs,
(i,x) <- zip [0..] xs, x == x]
j ds ds = if and [ds!!i == ds!!i | (i,i) <- js]
then Just [d | (k,d) <- zip ks (ds ++ ds), k]
else Nothing
in (Schema "" xs, j)
joinTables (X, j) r1 r2 returns r1 1 r2 , provided X = (r1 1
r2 ). j is used to join individual tuples.
joinTables :: (TableSchema, Joiner) -> Table -> Table
-> Table
joinTables (x,j) (Table _ ts) (Table _ ts) =
Table x (catMaybes [j t t | t <- ts, t <- ts])
r1 |><| r1 returns r1 1 r2 .
infixl 5 |><|
(|><|) :: Table -> Table -> Table
r1@(Table x _) |><| r2@(Table x _) =
joinTables (makeJoiner x x) r1 r2

46.3.4

46.4

Datum operations

addDatum :: Datum -> Datum -> Datum


addDatum DNull y = y
addDatum x DNull = x
addDatum (DString cs) y = DString (cs ++ (case y of
DString cs -> cs
DInteger i -> show i
DDouble d
-> show d
))
addDatum x (DString cs) = DString ((case x of
DInteger i -> show i
DDouble d
-> show d
) ++ cs)
addDatum (DDouble d) y = DDouble (d + (case y of
DDouble d -> d
DInteger i -> fromIntegral i
))
addDatum x (DDouble d) = DDouble ((case x of
DDouble d -> d
DInteger i -> fromIntegral i
) + d)
addDatum (DInteger i) (DInteger i) = DInteger (i + i)

Union

The union r1 r2 of relations r1 and r2 is defined by:

47

(Deprecated) Data.BSTree

1. (r1 ) = (r2 ) and (r1 r2 ) = (r1 )


2. r1 r2 = {t : t r1 t r2 }
r1 u r2 returns r1 r2 , provided (r1 ) = (r2 ). No check is
performed to ensure this precondition.
u :: Table -> Table -> Table
(Table x ts) u (Table _ ts) = Table x (ts ++ ts)

46.3.5

Difference

The difference r1 r2 of relations r1 and r2 is defined by:


1. (r1 ) = (r2 ) and (r1 r2 ) = (r1 )
2. r1 r2 = {t : t r1 t
/ r2 }
r1 dif r2 returns r1 r2 , provided (r1 ) = (r2 ). No check is
performed to ensure this precondition.
dif :: Table -> Table -> Table
(Table x ts) dif (Table _ ts) = Table x (ts \\ ts)

46.3.6

module ABR.Data.BSTree
{-# DEPRECATED "Use Data.Map instead." #-}
(
BSTree(..), emptyBST, nullBST, depthBST, updateBST,
deleteBST, lookupBST, memberBST, lookupGuard,
flattenBST, domBST, ranBST, countBST, leftBST,
rightBST, mapBST, pairs2BST, list2BST
)
where

47.1

Maintenance notes

Reviewed
Reviewed
Reviewed
anyway.
Reviewed

2015-02-09. Passed hlint.


2014-05-29: Made all the -Walls go away.
2012-10-31: Made compliant with new GHC. Deprecated

47.2

Language tweaks

2009-04-08: Changed to ABR.Data.BSTree.

Selection

The selection p (r) of relation r by predicate p is defined by:


1. (p (r)) = (r)
2. p (r) = {t r : p(r)}
select p r returns p (r).
select :: (Row -> Bool) -> Table -> Table
select p (Table x ts) = Table x (filter p ts)

46.3.7

Module ABR.Data.BSTree implements a depth/height balanced


(AVL) binary search tree abstract data type.

Renaming

The renaming B|A (r) in relation r of attribute A to attribute B is


defined by:
1. A (r), B
/ (r) and (B|A (r)) = ((r) {A}) {B}
2. B|A (r) = {t : t0 r t0 [B] = t[A] C ((r) {A}) t[C] =
t0 [C]}
renameTable B A r returns B|A (r).
renameTable :: Attribute -> Attribute -> Table -> Table
renameTable b a (Table x ts) =
Table (renameTableSchema b a x) ts
renameTableSchema B A X returns (X {A}) {B}.
renameTableSchema :: Attribute -> Attribute ->
TableSchema -> TableSchema
renameTableSchema b a (Schema _ as) =
Schema "" [if a == a then a else b | a <- as]

April 3, 2015

undefined :: String -> a


undefined label = error $
"Intentional undefined at module ABR.Data.BSTree, \
\ with label \"" ++ label ++ "\"."

47.3

BSTree type

A BSTree is either empty or a node containing a key, an associated


value and left and right sub-trees. Type key must be an instance of
type class Ord, so that < and == work.
The slope of each node is stored at the node to avoid recomputation.
data BSTree key value =
Empty
| Node !key !value
!(BSTree key value) !(BSTree key value) !Slope
deriving (Eq, Ord, Show)
All the functions in this module maintain the following invariant:
The depth of left and right sub-trees differ by no more than 1.
Associated with the left and right sub-trees is a slope value which
indicates the difference: depth left depth right.
type Slope = Int
This returns the slope of a node.

74

slope :: Ord k => BSTree k v -> Slope


slope (Node _ _ _ _ s) = s
slope Empty = error "ABR.Data.BSTree slope Empty"

updateBST f key value bst returns the new tree obtained by updating bst with the key and value. If the key already exists, f is
used to combine the two values. Use (\x _ -> x) to merely replace.

47.4

updateBST :: Ord k => (v -> v -> v) -> k -> v


-> BSTree k v -> BSTree k v

BSTree operations

emptyBST is an empty BSTree.


emptyBST :: Ord k => BSTree k v
emptyBST = Empty
nullBST t returns True iff t is empty.
nullBST :: Ord k => BSTree k v -> Bool
nullBST Empty
nullBST _

= True
= False

depthBST t returns the depth of a t.


Finding depth is O(depth) because of the slope information.
depthBST :: Ord k => BSTree k v -> Int
depthBST Empty
= 0
depthBST (Node _ _ l r s)
| s >= 0
= 1 + depthBST l
| otherwise
= 1 + depthBST r

updateBST f k v
= fst . update
where
update Empty
= (Node k v Empty Empty 0, 1)
update (Node k v l r s)
| k < k
= let (l, c) = update l
c = if s >= 0 && c == 1
in balance (Node k v l r (s
| k == k
= (Node k (f v v) l r s, 0)
| otherwise
= let (r, c) = update r
c = if s <= 0 && c == 1
in balance (Node k v l r (s

then 1 else 0
+ c), c)

then 1 else 0
- c), c)

deleteBST k t returns the new tree obtained by deleting the k and


its associated value from t.
deleteBST :: Ord k => k -> BSTree k v -> BSTree k v

deleteBST k
= fst . delete
where
delete Empty
balance :: Ord k => (BSTree k v, Int) -> (BSTree k v, Int)
= (Empty, 0)
balance (Node k v l r s, c)
delete (Node k v l r s)
| 1 < s
| k < k
= (shiftRight (Node k v l r s), c - 1)
= let (l, c) = delete l
| s < -1
c = if s == 1 && c == -1 then -1 else 0
= (shiftLeft (Node k v l r s), c - 1)
in balance (Node k v l r (s + c), c)
| otherwise
| k == k
= (Node k v l r s, c)
= join l r s
where
| otherwise
shiftRight (Node k v l r s)
= let (r, c) = delete r
| slope l == -1
c = if s == -1 && c == -1 then -1 else 0
= rotateRight (Node k v (rotateLeft l) r s)
in balance (Node k v l r (s - c), c)
| otherwise
join Empty r _
= rotateRight (Node k v l r s)
= (r, -1)
shiftRight _ = undefined "shiftRight _"
join l r s
shiftLeft (Node k v l r s)
= let ((l, c), k, v) = split l
| slope r == 1
s = s + c
= rotateLeft (Node k v l (rotateRight r) s)
c = if s == 1 && c == -1 then -1 else 0
| otherwise
in balance (Node k v l r s, c)
= rotateLeft (Node k v l r s)
split (Node k v l Empty _)
shiftLeft _ = undefined "shiftLeft _"
= ((l, -1), k, v)
rotateRight (Node k v (Node k v l r s) r s)
split (Node k v l r s)
= let (ss, ss)
= let ((r, c), k, v) = split r
= case (s, s) of
c = if s == -1 && c == -1 then -1 else 0
( 2, 0) -> (-1, 1)
in (balance (Node k v l r (s - c), c), k, v)
( 2, 1) -> ( 0, 0)
split Empty = error
( 2, 2) -> ( 0, -1)
"ABR.Data.BSTree split Empty"
( 1, 1) -> (-1, -1)
( 1, 0) -> (-1, 0)
( 1, -1) -> (-2, 0)
lookupBST k t returns Just v, where v is the value associated with
_
-> undefined "rotateRight _ 1"
k in t, or Nothing.
in Node k v l (Node k v r r ss) ss
rotateRight _ = undefined "rotateRight _ 2"
lookupBST :: Ord k => k -> BSTree k v -> Maybe v
rotateLeft (Node k v l (Node k v l r s) s)
= let (ss, ss)
lookupBST _ Empty
= case (s, s) of
= Nothing
(-2, 0) -> ( 1, -1)
lookupBST k (Node k v l r _)
(-2, -1) -> ( 0, 0)
| k < k
= lookupBST k l
(-2, -2) -> ( 0, 1)
| k == k
= Just v
(-1, -1) -> ( 1, 1)
| otherwise = lookupBST k r
(-1, 0) -> ( 1, 0)
(-1, 1) -> ( 2, 0)
memberBST k t returns True iff k occurs in t.
_
-> undefined"rotateLeft _ 1"
in Node k v (Node k v l l ss) r ss
memberBST :: Ord k => k -> BSTree k v -> Bool
rotateLeft _ = undefined "rotateLeft _ 2"
balance (Empty, _) = error
memberBST k t
"ABR.Data.BSTree balance Empty"
= case lookupBST k t of
After inserting or deleting a node the slope at a node may be 2 or
2. balance restores the 1 slope 1 invariant.

April 3, 2015

75

Nothing -> False


Just _ -> True
lookupGuard bst keys handler process tries to look up the keys.
If any are missing the handler is applied to the first missing key
otherwise the process is applied to the list of values successfully
looked up.
lookupGuard :: Ord a => BSTree a b -> [a] -> (a -> c)
-> ([b] -> c) -> c
lookupGuard bst keys handler process
= lookupGuard keys []
where
lookupGuard [] vals
= process vals
lookupGuard (k:ks) vals
= case lookupBST k bst of
Nothing
->
handler k
Just stuff ->
lookupGuard ks (vals ++ [stuff])
flattenBST t returns the list of tuples (k, v) in t in ascending order
of key.
flattenBST :: Ord k => BSTree k v -> [(k,v)]
flattenBST Empty
= []
flattenBST (Node k v Empty r _)
= (k, v) : flattenBST r
flattenBST (Node k v (Node k v l r _) r _)
= flattenBST (Node k v l (Node k v r r 0) 0)
domBST t returns the list of keys in t in ascending order of key.
domBST :: Ord k => BSTree k v -> [k]
domBST Empty
= []
domBST (Node k _ Empty r _)
= k : domBST r
domBST (Node k v (Node k v l r _) r _)
= domBST (Node k v l (Node k v r r 0) 0)
domBST t returns the list of values in t in ascending order of key.
ranBST :: Ord k => BSTree k v -> [v]
ranBST Empty
= []
ranBST (Node _ v Empty r _)
= v : ranBST r
ranBST (Node k v (Node k v l r _) r _)
= ranBST (Node k v l (Node k v r r 0) 0)

countBST :: Ord k => BSTree k v -> Int


countBST Empty
= 0
countBST (Node _ _ l r _) = countBST l + 1 + countBST r
leftBST t returns the left-most element of t. rightBST t returns
the right-most element of t.
leftBST, rightBST :: Ord k => BSTree k v -> Maybe (k, v)
leftBST Empty
= Nothing
leftBST (Node k v Empty _ _) = Just (k, v)
leftBST (Node _ _ l _ _)
= leftBST l
rightBST Empty
= Nothing
rightBST (Node k v _ Empty _) = Just (k, v)
rightBST (Node _ _ _ r _)
= rightBST r
mapBST f t returns the tree formed by applying f to all of the
values in t. The keys are not changed.
mapBST :: Ord k => (v -> v) -> BSTree k v -> BSTree k v
mapBST _ Empty
= Empty
mapBST f (Node k v l r s)
= Node k (f v) (mapBST f l) (mapBST f r) s

48

Module ABR.Data.HashTables implements hash tables in as efficient a manner as I can, while retaining as much polymorphism as
possible. The efficiency is made possible by exploiting the mutable
arrays built into the IO monad.
module ABR.Data.HashTables
{-# DEPRECATED "Use Data.HashTable instead." #-}
(
HashTable, newHT, updateHT, lookupHT, dumpHT
) where
import Data.Array.IO
import qualified Data.Map as M

48.1

pairs2BST :: Ord k => [(k,v)] -> BSTree k v


pairs2BST []
= Empty
pairs2BST ((k,v):kvs)
= updateBST const k v (pairs2BST kvs)
list2BST ks v converts a list of keys ks to a BSTree. The values
in the tree are all assigned v.
list2BST :: Ord k => [k] -> v -> BSTree k v
list2BST [] _
= Empty
list2BST (k:ks) v
= updateBST const k v (list2BST ks v)
countBST t returns the number of elements in t.

April 3, 2015

Maintenance notes

Reviewed 2015-02-09. Passed hlint.


Reviewed 2012-11-07: Deprecated. Removed the dependence on
ABR.Data.BSTree anyway.
Reviewed 2009-10-27: Changed to ABR.Data.HashTables.

48.2
pairs2BST kvs converts an association list kvs of pairs (k, v) to a
BSTree. If there are duplicate vs for a k, only the first is retained.

(Deprecated)
Data.HashTables

Data types

A HashTable is a mapping from keys to associated values. Access


is speeded by distributing the values across an array that can be
accessed in constant time using a hashing function to map the keys
to index values.
type HashTable key index value =
IOArray index (M.Map key value)
A hash table is an array of binary search trees. Each tree stores
all of the entries with the same hash value. The array type used
is the mutable array type that may be threaded through the IO
monad. Hence all the following functions are IO functions.

48.3

Creating a new hash table

newHT (lo, hi) returns a new empty hash table, where (lo, hi) is the
bounds on the array and therefore the range of the hashing function.
newHT :: (Ix ix, Ord key) =>
(ix,ix) -> IO (HashTable key ix value)
newHT range = newArray range M.empty

76

48.4

Updating an existing hash table

updateHT hashFun updateFun ht k v updates the hash table ht


with the key k and associated value v. The function hashFun maps
keys to hashing values. The function updateFun is used to combine
the new value v with any existing value already associated with this
key. Use (\x _ -> x) to merely replace the old value.
updateHT :: (Ix ix, Ord key) =>
(key -> ix) -> (value -> value -> value) ->
HashTable key ix value -> key -> value -> IO ()
updateHT hashFun updateFun ht k v = do
let i = hashFun k
t <- readArray ht i
writeArray ht i $ M.insertWith updateFun k v t

48.5

Looking up in a hash table

lookupHT hashFun k ht returns Just v, where v is the value associated with k in the hash table ht. If k is not in the hash table,
Nothing is returned. The function hashFun maps keys to hashing
values.
lookupHT :: (Ix ix, Ord key) =>
(key -> ix) -> key -> HashTable key ix value ->
IO (Maybe value)
lookupHT hashFun k ht = do
t <- readArray ht $ hashFun k
return $ M.lookup k t

49

(Deprecated) Data.Queue

The ABR.Data.Queue module implements the Queue ADT.


module ABR.Data.Queue
{-# DEPRECATED "Use Data.Sequence instead." #-}
(
Queue, emptyQ, isEmptyQ, attachQ, frontQ, detachQ,
extractQ
) where

49.1

Maintenance notes

Reviewed 2012-11-01: Deprecated.


Reviewed 2009-04-08: Changed to ABR.Data.Queue.
Reviewed: 2009-04-01. Theres nothing in the standard libraries
that really does this. There are more compicated queue pakages
in Hackage, but no compeling reason to drop this library for them.
Possible extensions: maybe some instances like Traversable, but as
needed.

49.2

Data type

A Queue is a first-in-first-out sequence.


type Queue a =
([a],[a])
A queue is a tuple of (fronts, backs) where:

48.6

Dumping a hash table

dumpHT ht prints the hash table ht in a fairly crude format, adequate for assessment of the hashing function.
dumpHT ::
(Ix ix, Enum ix, Ord key, Show key, Show value) =>
HashTable key ix value -> IO ()
dumpHT ht = do
let put ix = do
t <- readArray ht ix
print $ M.toList t
(lo,hi) <- getBounds ht
mapM_ put [lo..hi]

fronts is a list of foremost elements in order; and


backs is a list if the hindmost elements in reverse order.
For efficiency, we maintain the invariant: a queue is empty or fronts
is not empty.

49.3

Operations

emptyQ is an empty queue.


emptyQ :: Queue a
emptyQ = ([],[])
isEmptyQ q returns True iff queue q is empty.

48.7

Test harness

test path reads a file named path and counts the frequency of all
words. Note the example hashing function hf.

isEmptyQ :: Queue a -> Bool


isEmptyQ ([],_) = True
isEmptyQ _
= False
attachQ e q attaches e to the back of queue q.

test :: FilePath -> IO ()


test path = do
let size = 1009
inp <- readFile path
ht <- newHT (0, size - 1)
let hf = (mod size) . sum . map fromEnum
count1 w = updateHT hf (+) ht w 1
mapM_ count1 $ words inp
dumpHT ht
This version just uses plain BSTrees for comparison.
test :: FilePath -> IO ()
test path = do
inp <- readFile path
print $ M.toList
$ foldr (\w -> M.insertWith (+) w 1) M.empty
$ words inp
Hash tables using modulo type hashing functions as in the example
above work best if the size is prime. Use this to pick suitable primes.

attachQ :: a -> Queue a -> Queue a


attachQ e ([],_)
= ([e], [])
attachQ e (fronts, backs) = (fronts, e : backs)
frontQ q returns the value at the front of queue q.
frontQ :: Queue a -> a
frontQ ([],_)
=
error "Cant get frontQ of an empty queue."
frontQ (front:_,_) = front
detachQ q returns the element that was at the front of queue q
and the q after that element has been detached.
detachQ :: Queue a -> (a, Queue a)
detachQ ([],_)
=
error "Cant detachQ an empty queue."
detachQ ([front], backs)
= (front, (reverse backs, []))
detachQ (front:fronts,backs) = (front, (fronts, backs))
extractQ q returns the list of all elements in queue q.

primes :: [Int]
primes =
sieve [2..]
where
sieve (x:xs) =
x : sieve [x | x <- xs, x mod x /= 0]

April 3, 2015

extractQ :: Queue a -> [a]


extractQ ([],_) = []
extractQ q
= front : extractQ rest
where
(front, rest) = detachQ q

77

50

(Deprecated) Data.SparseSet

Module ABR.Data.SparseSet implements a set type where the elements are orderable, but too selected from too large a domain to
make an array implementation practical.
module ABR.Data.SparseSet
{-# DEPRECATED "Use Data.Set instead." #-}
(
SparseSet, emptySS, nullSS, insertSS, mkSS,
deleteSS, elemSS, notElemSS, flattenSS, list2SS,
countSS, isSubSet, unionSS, sectSS, diffSS
) where

unionSS :: Ord k => SparseSet k -> SparseSet k


-> SparseSet k
unionSS a b = foldr insertSS b $ flattenSS a
sectSS a b returns a b. This is faster if |a| < |b|.
sectSS :: Ord k => SparseSet k -> SparseSet k
-> SparseSet k
sectSS a b = list2SS $ filter (elemSS b) $ flattenSS a
diffSS a b returns a b.
diffSS :: Ord k => SparseSet k -> SparseSet k
-> SparseSet k
diffSS a b = foldl deleteSS a $ flattenSS b

import ABR.Data.BSTree

50.1

Maintenance notes

Reviewed 2015-02-09. Passed hlint.


Reviewed 2012-11-24: Deprecated.
Reviewed 2009-10-27: Changed to ABR.Data.SparseSet.

50.2

Data type

A SparseSet is a implemented with a height-balanced tree.


type SparseSet a =
BSTree a ()

50.3

Operations

emptySS is {}.
emptySS :: Ord k => SparseSet k
emptySS = emptyBST
nullSS s returns True iff s = {}.
nullSS :: Ord k => SparseSet k -> Bool
nullSS = nullBST
insertSS e s returns {e} s.
insertSS :: Ord k => k -> SparseSet k -> SparseSet k
insertSS k = updateBST const k ()

51

(Deprecated) Data.Set

Module ABR.Data.Set implements a set type where the elements


are orderable, but selected from too large a domain to make an array
implementation practical. The sets are implemented with a list.
module ABR.Data.Set
{-# DEPRECATED "Use Data.Set instead." #-}
(
Set, eset, set, set1, unset1, list, (.|), (.&), (.-),
(.+), (.<-), (.<), (.<=), card, smap, snull, select,
(.*), sprod, (.*.), sany, sall, sfoldl, sfoldl1,
sfoldr, sfoldr1, sunion, ssect
) where
import ABR.Data.List
import ABR.Text.Showing
import ABR.DeepSeq

51.1

Maintenance notes

Reviewed 2015-02-09. Passed hlint.


Reviewed 2012-11-24: Deprecated.
Reviewed 2009-10-27: Changed to ABR.Data.Set.

51.2

Data type

This representation of a Set is a list in strictly ascending order.


data Set a =

mkSS e returns {e}.


mkSS :: Ord k => k -> SparseSet k
mkSS e = insertSS e emptySS

Set [a]
deriving (Eq)

deleteSS s e returns s {e}.


deleteSS :: Ord k => SparseSet k -> k -> SparseSet k
deleteSS = flip deleteBST
elemSS s returns True iff e s.
elemSS :: Ord k => k -> SparseSet k -> Bool
elemSS = memberBST
notElemSS s returns True iff e 6 s.
notElemSS :: Ord k => k -> SparseSet k -> Bool
notElemSS e s = not $ memberBST e s
isSubSet a b returns True iff a b.
isSubSet :: Ord k => SparseSet k -> SparseSet k -> Bool
isSubSet a b = all (elemSS b) $ flattenSS a
flattenSS s returns the list of elements of s in ascending order.
flattenSS :: Ord k => SparseSet k -> [k]
flattenSS = domBST
list2SS xs returns the set of elements in xs.
list2SS :: Ord k => [k] -> SparseSet k
list2SS xs = list2BST xs ()
countSS s returns |s|.
countSS :: Ord k => SparseSet k -> Int
countSS = countBST

51.3

Operations

infixl 7 .&, .*, .*.


infixl 6 .|, .-, .+
infix 5 .<-, .<, .<=
eset is {}.
eset :: Set a
eset = Set []
set xs returns the set of elements in xs.
set :: (Ord a) => [a] -> Set a
set xs = Set (snub xs)
set1 x returns {x}.
set1 :: a -> Set a
set1 x = Set [x]
unset1 {x} returns x.
unset1 :: Set a -> a
unset1 (Set [x]) = x

unionSS a b returns a b. This is faster if |a| < |b|.

April 3, 2015

78

list A returns the list of elements in A.

card :: Set a -> Int

list :: Set a -> [a]

card (Set xs) = length xs

list (Set xs) = xs


smap f A returns {f x : x A}.
A .| B returns A B. A .& B returns A B. A .- B returns
A B.

smap :: (Ord b) => (a -> b) -> Set a -> Set b


smap f (Set xs) = Set (snub (map f xs))

(.|), (.&), (.-) :: (Ord a) => Set a -> Set a -> Set a
(Set as) .| (Set bs) = Set (umerge as bs)
where
umerge []
ys
= ys
umerge xs
[]
= xs
umerge (x:xs) (y:ys)
| x < y
= x : umerge xs
| x == y
=
umerge xs
| otherwise
= y : umerge (x:xs)
(Set as) .& (Set bs) = Set (imerge as bs)
where
imerge []
ys
= []
imerge xs
[]
= []
imerge (x:xs) (y:ys)
| x < y
=
imerge xs
| x == y
= x : imerge xs
| otherwise
=
imerge (x:xs)
(Set as) .- (Set bs) = Set (smerge as bs)
where
smerge []
ys
= []
smerge xs
[]
= xs
smerge (x:xs) (y:ys)
| x < y
= x : smerge xs
| x == y
=
smerge xs
| otherwise
=
smerge (x:xs)

snull A returns True iff A = {}.


snull :: Set a -> Bool
snull (Set xs) = null xs
(y:ys)
(y:ys)
ys

select P A returns {x : x A and P (x)}.


select :: (a -> Bool) -> Set a -> Set a
select f (Set xs) = Set (filter f xs)

(y:ys)
ys
ys

A .* B returns A B =

{A, B}.

(.*) :: Ord a => Set a -> Set a -> Set (Set a)


a .* b = sprod (set [a, b])
sprod {A1 , . . . An } returns

(y:ys)
ys
ys

A .+ x returns A {x}.
(.+) :: (Ord a) => Set a -> a -> Set a

N
N
{A1 , . . . An }, where
{} = {{}},
N

{A} = {{a} : a A}, and


{A1 , . . . An } = A1 . . . An =
{{a1 , . . . an } : (a1 , . . . an ) A1 . . . An }
sprod :: Ord a => Set (Set a) -> Set (Set a)
sprod (Set []) = Set [Set []]
sprod (Set [Set xs]) = Set (map set1 xs)
sprod (Set as) = (set . map set . cartProd . map list) as

(Set as) .+ x = Set (insert as)


where
insert []
= [x]
insert (y:ys)
| x < y
= x : y : ys
| x == y
= y : ys
| otherwise = y : insert ys

(.*.) :: (Ord a, Ord b) =>


Set a -> Set b -> Set (a,b)

x .<- A returns True iff x A.

sall P A returns True iff a A, P (a). sany P A returns True


iff a A, P (a).

(.<-) :: (Ord a) => a -> Set a -> Bool

sall, sany :: (a -> Bool) -> Set a -> Bool

x .<- (Set as) = elem as


where
elem []
= False
elem (y:ys)
| x < y
= False
| x == y
= True
| otherwise = elem ys

sall f (Set xs) = all f xs


sany f (Set xs) = any f xs

A .< B returns True iff A B. A .<= B returns True iff A B.


(.<), (.<=) :: (Ord a) => Set a -> Set a -> Bool
Set as .< Set bs = Set as .<= Set bs
&& length as < length bs
(Set as) .<= (Set bs) = iss as bs
where
iss []
ys = True
iss xs
[] = False
iss (x:xs) (y:ys)
| x < y
= False
| x == y
= iss xs ys
| otherwise = iss (x:xs) ys

A .*. B returns A B.

(Set as) .*. (Set bs) = Set [(a,b) | a <- as, b <- bs]

sfoldl , sfoldr , sfoldl1 and sfoldr1 are analogues of foldl,


foldr, foldl1 and foldr1 respectively.
sfoldl :: (a -> b -> a) -> a -> Set b -> a
sfoldr :: (a -> b -> b) -> b -> Set a -> b
sfoldl1, sfoldr1:: (a -> a -> a) -> Set a -> a
sfoldl f z (Set xs) = foldl
sfoldr f z (Set xs) = foldr
sfoldl1 f (Set xs) = foldl1
sfoldr1 f (Set xs) = foldr1

f
f
f
f

z xs
z xs
xs
xs

sunion {A1 , . . . , An } returns


T {A1 , . . . , An } = A1 . . . An .
ssect {A1 , . . . , An } returns {A1 , . . . , An } = A1 . . . An .
sunion, ssect :: Ord a => Set (Set a) -> Set a
sunion = sfoldl (.|) eset
ssect a | snull a
= eset
| otherwise = sfoldl1 (.&) a

card A returns |A|.

April 3, 2015

79

51.4

Instances

51.4.1

52.4.2

Ord

instance (Ord a) => Ord (Set a) where


compare (Set as) (Set bs) =
case compare (length as) (length bs) of
EQ -> compare as bs
c -> c

51.4.2

Showing

instance (Show a) => Show (Set a) where


showsPrec p (Set xs) = showChar {
. showWithSep "," xs . showChar }

51.4.3

DeepSeq

instance (DeepSeq a) => DeepSeq (Set a) where


deepSeq (Set xs) = deepSeq xs

52

(Deprecated) DeepSeq

Module ABR.DeepSeq was pinched from Dean Herington, who says:


The prelude support for strict evaluation, seq and ($!),
evaluate only enough to ensure that the value being
forced is not bottom. In your case you need a deeper
evaluation to be forced.
A clean (though somewhat tedious) way to achieve what
you need is with the deepSeq function from the following
module.
The DeepSeq class provides a method deepSeq that is
similar to seq except that it forces deep evaluation of its
first argument before returning its second argument.
Instances of DeepSeq are provided for Prelude types.
Other instances must be supplied by users of this module.
module ABR.DeepSeq
{-# DEPRECATED "Use Control.DeepSeq instead." #-}
(DeepSeq(..), ($!!)) where

instance (DeepSeq a, DeepSeq b) =>


DeepSeq (a,b) where
deepSeq (a,b) y = deepSeq a $ deepSeq b y
instance (DeepSeq a, DeepSeq b, DeepSeq c) =>
DeepSeq (a,b,c) where
deepSeq (a,b,c) y = deepSeq a $ deepSeq b $ deepSeq c y
instance (DeepSeq a,DeepSeq b,DeepSeq c,DeepSeq d) =>
DeepSeq (a,b,c,d) where
deepSeq (a,b,c,d) y = deepSeq a $ deepSeq b $
deepSeq c $ deepSeq d y
instance (DeepSeq a, DeepSeq b, DeepSeq c, DeepSeq d,
DeepSeq e) => DeepSeq (a,b,c,d,e) where
deepSeq (a,b,c,d,e) y = deepSeq a $ deepSeq b $
deepSeq c $ deepSeq d $ deepSeq e y
instance (DeepSeq a, DeepSeq b, DeepSeq c, DeepSeq d,
DeepSeq e, DeepSeq f) => DeepSeq (a,b,c,d,e,f) where
deepSeq (a,b,c,d,e,f) y = deepSeq a $ deepSeq b $
deepSeq c $ deepSeq d $ deepSeq e $ deepSeq f y
instance (DeepSeq a, DeepSeq b, DeepSeq c, DeepSeq d,
DeepSeq e,DeepSeq f,DeepSeq g) =>
DeepSeq (a,b,c,d,e,f,g) where
deepSeq (a,b,c,d,e,f,g) y = deepSeq a $ deepSeq b $
deepSeq c $ deepSeq d $ deepSeq e $ deepSeq f $
deepSeq g y

52.4.3

Maintenance notes

Reviewed 2012-11-24: Deprecated.

52.2

List instance

instance (DeepSeq a) => DeepSeq [a] where


deepSeq [] y = y
deepSeq (x:xs) y = deepSeq x $ deepSeq xs y

52.4.4

Maybe instance

instance (DeepSeq a) => DeepSeq (Maybe a) where


deepSeq Nothing y = y
deepSeq (Just x) y = deepSeq x y

52.4.5

52.1

Tuple instances

Either instance

instance (DeepSeq a, DeepSeq b) =>


DeepSeq (Either a b) where
deepSeq (Left a) y = deepSeq a y
deepSeq (Right b) y = deepSeq b y

Class Definition

DeepSeq has only one method, deepSeq x y deeply evaluates x


and then returns y.

53

class

Module ABR.Data.BSTree implements a depth/height balanced


(AVL) binary search tree abstract data type.

DeepSeq a where

deepSeq :: a -> b -> b


deepSeq = seq
-- default, for simple cases

52.3

Infix operator

f $!! x deeply evaluates x and then returns f x.

($!!) :: (DeepSeq a) => (a -> b) -> a -> b


f $!! x = x deepSeq f x

52.4.1
instance
instance
instance
instance
instance
instance
instance
instance

module ABR.DeepSeq.BStree
{-# DEPRECATED
"Use Data.Map and Control.DeepSeq instead." #-}
where
import ABR.DeepSeq
import ABR.Data.BSTree

infixr 0 deepSeq, $!!

52.4

(Deprecated) DeepSeq.BStree

Instance Declarations

53.1

Maintenance notes

Reviewed 2009-04-08: Separated from ABR.Data.BSTree.

Simple instances
DeepSeq
DeepSeq
DeepSeq
DeepSeq
DeepSeq
DeepSeq
DeepSeq
DeepSeq

April 3, 2015

()
Bool
Char
Ordering
Integer
Int
Float
Double

where
where
where
where
where
where
where
where

{}
{}
{}
{}
{}
{}
{}
{}

53.2

Instance declaration

instance (DeepSeq k, DeepSeq v, Ord k) =>


DeepSeq (BSTree k v) where
deepSeq t x = case t of
Empty
-> x
Node k v l r s -> deepSeq k $ deepSeq v $
deepSeq l $ deepSeq r $ deepSeq s x

80

54

(Deprecated)
Logic.Qualification

Module ABR.Logic.Qualification implements qualification for


CDL.
{-# LANGUAGE FlexibleInstances #-}
module ABR.Logic.Qualification
{-# DEPRECATED "Ill-conceived, I think." #-}
(
Qualifiable(..)
)
where

54.1

Maintenance notes

Reviewed 2013-11-24: Deprecated.

54.2

Qualifiable class

Class Qualifiable overloads methods for qualifying names.


class Qualifiable a where
qualify n x prepends n. to the name of x.
qualify :: String -> a -> a

54.3

Instance declarations

54.3.1

Qualification

instance Qualifiable String where


qualify n cs |. elem cs = cs
| otherwise
= n ++ . : cs

References
[1] Graham Hutton. Higher-order functions for parsing. J. Functional Programming, 2:323343, 1992. 16
[2] Jeroen Fokker. Functional parsers. In Jeuring and Meijer [6],
pages 123. 16
[3] K. H. Rosen. Discrete Mathematics and Its Applications.
McGraw-Hill, 5th edition, 2005. 32
[4] F. Rabhi and G. Lapalme. Algorithms: A Functional Programming Approach. Addison-Wesley, 1999. 43
[5] J. Launchbury. Graph algorithms with a functional flavour. In
Jeuring and Meijer [6], pages 308331. 43
[6] Johan Jeuring and Erik Meijer, editors. Advanced Functional
Programming, volume 925 of LNCS. Springer-Verlag, 1995. 2, 5

Index
*?, 9
++-++, 46
++.++, 31
++/++, 31
+:, 11
+?, 9
.*, 79
.*., 79
.+, 79
.-, 79
.<, 79
.<-, 79
.<=, 79
.&, 79
.|, 79
:->, 26
:->-, 34
:>->, 34
<++>, 16
<&, 15
<&>, 15

April 3, 2015

<&&>, 16
<|>, 15
??, 9
@>, 15
#>, 15
#?, 9
$!!, 80
%>, 16
&>, 15
&?, 9
&%>, 16
!!!, 13, 14
ABR.CGI, 49
ABR.Control.Check, 9
ABR.Control.List, 9
ABR.Control.Map, 9
ABR.Data.BSTree, 74, 80
ABR.Data.Graph, 66
ABR.Data.HashTables, 76
ABR.Data.List, 10
ABR.Data.NameTable, 12
ABR.Data.Queue, 77
ABR.Data.Set, 78
ABR.Data.SparseSet, 78
ABR.Data.Supply, 13
ABR.Database.Relational, 73
ABR.Daytime, 52
ABR.Debug.Array, 13
ABR.Debug.IArray, 14
ABR.DeepSeq, 80
ABR.Fie.Versions, 48
ABR.File.Lock, 47
ABR.Graphics.EPS, 41
ABR.Graphics.Geometry, 40
ABR.HaskellLexer, 54
ABR.Logic.Arguments, 35
ABR.Logic.Atoms, 36
ABR.Logic.Literals, 37
ABR.Logic.Qualification, 81
ABR.Logic.QuineMcClusky, 38
ABR.Logic.Variables, 34
ABR.MySQL, 72
ABR.MySQLCBinding, 69
ABR.Parser, 14
ABR.Parser.Checks, 19
ABR.Parser.Lexers, 17
ABR.Parser.Predicates, 19
ABR.PlayingCards, 63
ABR.Poker, 64
ABR.SendMail, 66
ABR.Text.Configs, 19
ABR.Text.CSV, 27
ABR.Text.JSON, 23
ABR.Text.Markup, 27
ABR.Text.Showing, 31
ABR.Text.String, 29
ABR.Util.Args, 5
ABR.Util.Errors, 6
ABR.Util.Open, 7
ABR.Util.Pos, 7
ABR.Util.Time, 7
ABRHLibs.zip, 4
accumArray, 14
Ace, 64
addressE, 50
addressN, 51
aE, 50
allSuccRanks, 65
allUnique, 11
alsoNotSat, 15
alsoSat, 15
ampersand, 7
Analyser, 14
Angle, 40
arc, 46
areaE, 50
areAnyLocked, 47

81

areSuccRanks, 64
argListP, 36
Argument, 35
argument
in atoms, 35
argumentP, 35
arguments
in atoms, 33, 34, 36
array, 14
assertFlagMinus, 5
assertFlagPlus, 5
Atom, 36
atom, 35, 36
atomNameP, 36
atomP, 36
attachQ, 77
bagElem, 11
baseE , 50
bE, 50
beats, 65
best5, 66
better, 65
bigE, 50
bigN, 51
blockGuard, 48
blockquoteE, 50
blockquoteN, 51
bN, 51
bodyE, 50
bodyN, 51
Box, 40
box, 47
BPS, 42
bpsToEps, 42
brE , 50
brN , 50
BSTree, 74
captionE, 50
captionN, 51
Card, 64
card, 79
cardinalL, 18
cartProd, 10
catenateWith, 31
centerE, 50
centerN, 51
CFlag, 19
Check, 9
CheckFail, 9
checkLex, 19
checkParse, 19
CheckPass, 9
CheckResult, 9
chop, 12
chops, 12
CIntegral, 33
citeE, 50
citeN, 51
CList, 19
closepath, 46
Clubs, 64
CNamed, 33
codeE, 50
codeN, 51
Col, 7
collecting
constants, 35
variables, 35
combinations, 10
comp, 37
compareCards, 65
compareGroups, 65
compareHands, 65
compiling, 4
Complementable, 37
Config, 19

April 3, 2015

Configs, 20
configsL, 20
configsP, 20
cons, 15
Const, 35
Constant, 33
constant, 33, 35
constantP, 33
constants
collecting, 35
Could, 14
countBST, 76
countSS, 78
CParam, 19
createDirectory, 49
CSet, 19
CString, 33
currentTime, 8
dataSatisfies, 15
dataSatisfies, 15
dateThenTime1, 8
dateThenTime2, 8
dayAndTimeP, 54
Daytime, 52
daytimeL, 52
daytimeP, 54
ddE, 50
ddN, 51
Deck, 64
deck52, 64
deck54, 64
declarations, 62
declared, 63
DeepSeq, 36
DeepSeq, 80
deepSeq, 80
deleteBST, 75
deleteFlag, 5
deleteParam, 5
deleteSS, 78
delimiter, 7
deliterate, 54
depthBST, 75
detachQ, 77
Diamonds, 64
diffDay, 8
diffHour, 8
diffMin, 8
diffSec, 8
diffSS, 78
dirE, 50
dirN, 51
discardInners, 62
disjoint, 12
distAroundOval, 41
distribution, 4
divE, 50
dlE, 50
dlN, 51
docType, 50
domBST, 76
dropEach, 10
dropWhite, 18
Dsh, 38
dtE, 50
dtHrs, 52
dtMins, 52
dtN, 51
dtSecs, 52
dumpFormData, 52
dumpHT, 77
duplicates, 11
ED, 32
elemSS, 78
emE, 50
emN, 51

82

emptyBST, 75
emptyOptions, 5
emptyQ, 77
emptySS, 78
encodeHTML, 27
encodeHTML, 27
endA, 15
endL, 17
enString, 31
Enum mysql option, 69
EPS, 42
epsDraw, 42
EPSDrawable, 42
epsilonA, 15
errMsg, 17
Error, 14
errorA, 15
ErrorCategory, 6
eset, 78
extractQ, 77
Fail, 14
failA, 15
FD, 32
fields, 30
fileModTime, 8
findClosest, 31
findOpts, 5
findSubset, 12
fixedL, 18
fixNewlines, 31
fixNewlines, 31
FlagMinus, 5
FlagPlus, 5
FlagS, 5
flattenBST, 76
flattenSS, 78
floatL, 18
Flush, 65
FontBlock, 46
fontE, 50
FontString, 46
FontTag, 45
formatLegacyTime, 8
FormattedDouble, 32
formatTime, 8
formatUTCTime, 8
formE, 50
formfeedL, 17
fragments, 10
fragments, 10
Friday, 52
frontQ, 77
fsWidth, 46
ftStr, 45
ftUnder, 45
ftWidth, 46
FullHouse, 65
Garbage, 65
GD, 32
GeoNum, 41
getAtoms, 36
getConstants, 33
getCONTENT LENGTH, 51
getCurrentLegacyTime, 8
getCurrentLocalTime, 8
getCurrentUTCTime, 8
getCurrentZonedTime, 8
getFormData, 52
getFormData, 52
getNames, 49
getParam, 22
getPATH INFO, 51
getPos, 7
getQUERY STRING, 51
getSCRIPT NAME, 51
getScriptDirectory, 51

April 3, 2015

getVariables, 34
glob, 6
grestore, 46
ground, 34
ground1, 34
Groundable, 34
Grounding, 35
groupByRank, 64
groupBySuit, 64
gsave, 46
h1E, 50
h1N, 51
h2E, 50
h2N, 51
h3E, 50
h3N, 51
h4E, 50
h4N, 51
h5E, 50
h5N, 51
h6E, 50
h6N, 51
Hand, 64
HandType, 65
handType, 65
HasAtoms, 36
HasConstants, 33
hasConstants, 33
HashTable, 76
HasKind, 32
HasPos, 7
HasVariables, 34
hasVariables, 34
HAttributes, 50
headE, 50
headN, 51
Hearts, 64
Helvetica10, 45
Helvetica10Bold, 45
Helvetica10BoldOblique, 45
helvetica10BoldObliqueWidth, 45
helvetica10BoldWidth, 44
Helvetica10Oblique, 45
helvetica10ObliqueWidth, 44
helvetica10Width, 43
hrE , 50
hrN , 50
HTag, 50
htmlE, 50
htmlError, 51
htmlN, 51
htmlT, 51
iBox, 41
iE, 50
iGeo, 41
imgE , 50
iN, 51
inInterval, 54
inputE , 50
insertName, 13
insertParam, 5
insertSS, 78
insetBox, 41
insetSeg, 41
integerP, 33
interleave, 10
iPoint, 41
isCardinal, 19
isEmptyQ, 77
isFatal, 6
isFixed, 19
isFloat, 19
isFlush, 65
isFullHouse, 65
isGarbage, 65
isindexE , 50

83

isindexN , 50
isLockedFile, 47
isPair, 65
isPoker, 65
isProperSubset, 11
isSignedCardinal, 19
isSignedFixed, 19
isSignedFloat, 19
isStraight, 65
isStraightFlush, 65
isSubSequence, 12
isSubSet, 78
isSubset, 11
isTriple, 65
isTwoPair, 65
Jack, 64
jGet, 26
jmArray, 24
jmFalse, 24
jmNull, 24
jmNumber, 24
jmObject, 24
jmString, 24
jmTrue, 24
joinBPS, 42
Joker, 64
JPropertyName, 23
JQBool, 26
JQDbe, 26
JQElem, 26
JQElems, 26
JQInt, 26
JQIsNull, 26
JQProp, 26
JQProps, 26
JQStr, 26
JQuery, 26
JResult, 26
justifyColumn, 30
JValue, 23
kbdE, 50
kbdN, 51
Kind, 32
kind, 32
kindCheck, 32
kindCheckList, 32
Kinds, 32
King, 64
KIntegral, 32
KNamed, 32
KString, 32
KTuple, 32
kUnify, 32
KUnknown, 32
latestDate, 48
latex2html, 28
leastRightShift, 41
leftBST, 76
LegacyTime, 8
LegacyTimes, 8
Lexeme, 16
Lexer, 16
lexerL, 25
liE, 50
liN, 51
Line, 7, 41
lineNo, 17
LineSeg, 41
lineto, 46
linkE , 50
list, 79
list2BST, 76
list2SS, 78
listL, 17
Literal, 37

April 3, 2015

literalL, 16
literalP, 17
lJustify, 30
lJustify, 30
lockFile, 47
lockFiles, 47
lockGuard, 48
Logic.Constants, 33
lookupBST, 75
lookupConfig, 21
lookupFlag, 6
lookupGuard, 10, 76
lookupHT, 77
lookupName, 13
lookupParam, 6, 22
lookupQueue, 6
Makefile, 4
MakeFontTags, 46
makeFontTags, 46
makeFontTagsPrec, 46
makeFormattedDouble, 32
makeHTMLSafe, 27
makeLatexSafe, 28
makeNameArray, 13
makeTable, 30
makeTableL, 30
makeTableMR, 30
many, 15
manyUntil, 15
mapBST, 76
mapE, 50
meet, 12
memberBST, 75
menuE, 50
menuN, 51
merge, 10
metaE , 50
mimeHeader, 50
mkSS, 78
mnub, 11
moduleName, 62
Monday, 52
moveto, 46
Msg, 14
msort, 10
My bool, 69
My ulonglong, 69
myClose, 72
myConnect, 72
myFetch, 72
myQuery, 72
MYSQL, 69
MySQL, 72
mysql affected rows, 69
mysql change user, 69
mysql character set name, 69
mysql close, 69
mysql data seek, 70
mysql errno, 70
mysql error, 70
mysql fetch field, 70
mysql fetch field direct, 70
mysql fetch fields, 70
mysql fetch lengths, 70
mysql fetch row, 70
MYSQL FIELD, 69
mysql field count, 70
MYSQL FIELD OFFSET, 69
mysql field seek, 70
mysql field tell, 70
mysql free result, 70
mysql get client info, 70
mysql get host info, 70
mysql get proto info, 70
mysql get server info, 70
mysql info, 70
mysql init, 70

84

mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql
MYSQL
MYSQL
MYSQL
mysql
mysql
mysql
mysql
mysql
mysql
mysql
mysql

insert id, 70
kill, 70
list dbs, 70
list fields, 70
list processes, 70
list tables, 71
num fields, 71
num rows, 71
options, 71
ping, 71
query, 71
real connect, 71
real escape string, 71
real query, 71
RES, 69
ROW, 69
ROW OFFSET, 69
row seek, 71
row tell, 71
select db, 71
shutdown, 71
stat, 71
store result, 71
thread id, 71
use result, 71

NameArray, 13
nameCmp, 31
nameLT, 31
NameTable, 13
Neg, 37
neg, 37
Negatable, 37
netBox, 41
newHT, 76
newlineL, 17
newNameTable, 13
newpath, 46
newSupply, 13
nextidE , 50
nofail, 16
nofail, 16
noSuperSets, 12
notElemSS, 78
notSubSequence, 12
nullBST, 75
nullSS, 78
NullSub, 34
odiff, 11
offside, 61
OK, 14
olE, 50
olN, 51
One, 38
openTool, 7
openURL, 7
optional, 15
optionE, 50
Options, 5
OptSpec, 5
OptVal, 5
osect, 11
ounion, 12
Pair, 65
pairs2BST, 76
pam, 9
ParamMissingValue, 5
ParamQueue, 5
ParamS, 5
ParamValue, 5
Parser, 17
parsers, 35
pE, 50
peekNext, 13
permutations, 10
permutations, 10

April 3, 2015

pLiteralP, 37
pN, 51
Point, 40
Poker, 65
popTemplate, 22
Pos, 7, 37
pos, 37
powSet, 11
powSet, 11
powSet ge1, 11
powSet ge1, 11
pPlus, 12
precedes, 7
preE, 50
preLex, 16
preN, 51
printDocType, 50
printMimeHeader, 50
programL, 55
promoteMethods, 62
Prop, 36
properSublists, 11
PS, 42
psStr, 46
purgeVersions, 48
put, 50
put, 50
QMBit, 38
qmSimplify, 40
Qualifiable, 81
qualify, 81
Queen, 64
Queue, 77
QueueS, 5
R10, 64
R2, 64
R3, 64
R4, 64
R5, 64
R6, 64
R7, 64
R8, 64
R9, 64
Rank, 64
rank, 64
ranks, 64
read, 21
readFile, 49
readLatest, 48
removeR, 49
removeVersions, 48
rename, 34
replace1, 12
replaceAll, 12
returnFail, 6
returnL, 17
returnPass, 6
returnWarn, 6
rightBST, 76
rJustify, 30
rJustify, 30
sall, 79
sampE, 50
sampN, 51
sany, 79
satisfyL, 16
Saturday, 52
sectSS, 78
segToLine, 41
select, 79
selectE, 50
sendMail, 66
separate, 10
Set, 78
set, 78

85

set1, 78
setUpFonts, 42
sfoldl, 79
sfoldl1, 79
sfoldr, 79
sfoldr1, 79
shiftBoxes, 41
show , 46
showConfigs, 21
showDT24, 54
showFD, 32
Showing, 35
showWithSep, 32
showWithTerm, 32
shuffle, 64
signedCardinalL, 18
signedFixedL, 18
SimpleLit, 4
smallE, 50
smallN, 51
smap, 79
snub, 11
snull, 79
soft, 16
some, 15
someUntil, 15
sortByLength, 10
sortByRankSuit, 64
sortBySuitRank, 64
Space, 45
spaceColumns, 30
spaceL, 17
spaces, 31
Spades, 64
SparseSet, 78
split, 10
sprod, 79
ssect, 79
Straight, 65
StraightFlush, 65
stringL, 18
stroke, 46
strongE, 50
strongN, 51
styleE, 50
subBag, 11
subE, 50
subHashNames, 31
subHashNums, 31
subN, 51
subsSuffix, 12
subst, 31
Substitution, 34
substs, 31
succeedA, 14
Suit, 64
suit, 64
suits, 64
Sunday, 52
sunion, 79
supE, 50
supN, 51
Supply, 13
supplyNext, 13
Symbol10, 45
symbol10Width, 45
tabL, 17
tableE, 50
tableN, 51
Tag, 16
tagFilter, 16
tagP, 17
tdE, 50
tdN, 51
textareaE, 50
thE, 50
thN, 51

April 3, 2015

Thursday, 52
ties, 65
TimeFormat, 8
Times10, 45
Times10Ital, 45
times10ItalWidth, 43
times10Width, 42
titleE, 50
titleN, 51
TLP, 16
TLPs, 16
tokenL, 17
tomorrow, 54
total, 16
translate, 46
trE, 50
trim, 30
trim2, 12
trimN, 12
Triple, 65
trN, 51
ttE, 50
ttN, 51
Tuesday, 52
TwoPair, 65
uE, 50
ulE, 50
ulN, 51
uN, 51
unfields, 30
unionSS, 78
unlex, 62
unlockFile, 47
unlockFiles, 47
unset1, 78
unString, 31
updateBST, 75
updateConfig, 21
updateHT, 77
utcToLocalTime, 8
utcToZonedTime, 8
valueP, 25
Var, 35
varE, 50
Variable, 34
variable, 34
variableP, 34
variables
collecting, 35
varN, 51
vertabL, 17
warnMsg, 17
weCat, 6
Wednesday, 52
Weekday, 52
weekdayP, 53
WEMessage, 6
weMsg, 6
wePos, 6
WEResult, 6
whitespaceL, 18
wordWrap, 29
wrapWithinWidth, 46
writeFile, 49
writeNew, 48
writeNew, 48
writeNew, 48
yesterday, 54
Zer, 38

86

Anda mungkin juga menyukai