Clojure PDF
Clojure PDF
Rich Hickey
i
ii CONTENTS
2.4.1 Persistence . . . . . . . . . . . . . . . . . . . . . . . . 39
2.4.2 Persistent Red Black Trees . . . . . . . . . . . . . . . 39
2.4.3 Node Structure . . . . . . . . . . . . . . . . . . . . . . 40
2.4.4 The Black Node implementation . . . . . . . . . . . . 42
2.4.5 Constructing a Black Node . . . . . . . . . . . . . . . 45
2.4.6 The Red Node implementation . . . . . . . . . . . . . 46
2.4.7 Constructing a Red Node . . . . . . . . . . . . . . . . 49
2.4.8 Okasakis balance cases . . . . . . . . . . . . . . . . . 50
2.4.9 Deleting a Node . . . . . . . . . . . . . . . . . . . . . 52
2.4.10 Clojures balance cases . . . . . . . . . . . . . . . . . . 53
2.4.11 Replacing a Node . . . . . . . . . . . . . . . . . . . . . 55
2.5 Immutable Data Structures . . . . . . . . . . . . . . . . . . . 55
2.6 Bit-Partitioned Hash Tries . . . . . . . . . . . . . . . . . . . . 55
2.6.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.6.2 Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.6.3 Hashmaps . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.6.4 Seqs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
2.7 Records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.8 Java Interoperability . . . . . . . . . . . . . . . . . . . . . . . 62
2.8.1 Language primitives . . . . . . . . . . . . . . . . . . . 62
2.8.2 Clojure calling Java . . . . . . . . . . . . . . . . . . . 64
2.9 Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.10 Argument Deconstruction . . . . . . . . . . . . . . . . . . . . 65
2.11 The Read-Eval-Print Loop . . . . . . . . . . . . . . . . . . . . 65
2.12 Special Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.13 Reader Macros . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.14 Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.15 Dynamic Comopilation . . . . . . . . . . . . . . . . . . . . . . 66
2.16 Ahead-Of-Time Comopilation . . . . . . . . . . . . . . . . . . 66
2.17 Lazy Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.18 Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.19 Concurrency . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.20 Identity and State . . . . . . . . . . . . . . . . . . . . . . . . 66
2.21 Software Transactional Memory . . . . . . . . . . . . . . . . . 67
2.22 Symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.23 The Lisp Reader . . . . . . . . . . . . . . . . . . . . . . . . . 69
2.23.1 Reader Syntax Macros . . . . . . . . . . . . . . . . . . 71
2.23.2 The String reader macro . . . . . . . . . . . . . . . . . 72
2.23.3 The Comment reader macro . . . . . . . . . . . . . . . 74
2.23.4 The Wrapping reader macro . . . . . . . . . . . . . . . 74
2.23.5 The Meta reader macro . . . . . . . . . . . . . . . . . 75
2.23.6 The SyntaxQuote reader macro . . . . . . . . . . . . . 76
2.23.7 The Unquote reader macro . . . . . . . . . . . . . . . 79
2.23.8 The List reader macro . . . . . . . . . . . . . . . . . . 80
2.23.9 The Unmatched Delimiter reader macro . . . . . . . . 80
2.23.10 The Vector reader macro . . . . . . . . . . . . . . . . 80
CONTENTS iii
7 jvm/clojure/asm/ 163
7.1 AnnotationVisitor.java . . . . . . . . . . . . . . . . . . . . . . 163
7.2 AnnotationWriter.java . . . . . . . . . . . . . . . . . . . . . . 165
7.3 Attribute.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
7.4 ByteVector.java . . . . . . . . . . . . . . . . . . . . . . . . . . 176
7.5 ClassAdapter.java . . . . . . . . . . . . . . . . . . . . . . . . 183
7.6 ClassReader.java . . . . . . . . . . . . . . . . . . . . . . . . . 185
7.7 ClassVisitor.java . . . . . . . . . . . . . . . . . . . . . . . . . 229
7.8 ClassWriter.java . . . . . . . . . . . . . . . . . . . . . . . . . 233
7.9 Edge.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
7.10 FieldVisitor.java . . . . . . . . . . . . . . . . . . . . . . . . . 263
7.11 FieldWriter.java . . . . . . . . . . . . . . . . . . . . . . . . . . 264
7.12 Frame.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
7.13 Handler.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
7.14 Item.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
7.15 Label.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
7.16 MethodAdapter.java . . . . . . . . . . . . . . . . . . . . . . . 314
7.17 MethodVisitor.java . . . . . . . . . . . . . . . . . . . . . . . . 317
7.18 MethodWriter.java . . . . . . . . . . . . . . . . . . . . . . . . 326
7.19 Opcodes.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
7.20 Type.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
8 jvm/clojure/asm/commons 413
8.1 AdviceAdapter.java . . . . . . . . . . . . . . . . . . . . . . . . 413
8.2 AnalyzerAdapter.java . . . . . . . . . . . . . . . . . . . . . . 426
8.3 CodeSizeEvaluator.java . . . . . . . . . . . . . . . . . . . . . 445
8.4 EmptyVisitor.java . . . . . . . . . . . . . . . . . . . . . . . . 449
8.5 GeneratorAdapter.java . . . . . . . . . . . . . . . . . . . . . . 453
8.6 LocalVariablesSorter.java . . . . . . . . . . . . . . . . . . . . 484
CONTENTS v
9 jvm/clojure/lang/ 509
9.1 AFn.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
9.2 AFunction.java . . . . . . . . . . . . . . . . . . . . . . . . . . 519
9.3 Agent.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 520
9.4 AMapEntry.java . . . . . . . . . . . . . . . . . . . . . . . . . 527
9.5 APersistentMap.java . . . . . . . . . . . . . . . . . . . . . . . 530
9.6 APersistentSet.java . . . . . . . . . . . . . . . . . . . . . . . . 538
9.7 APersistentVector.java . . . . . . . . . . . . . . . . . . . . . . 541
9.8 AReference.java . . . . . . . . . . . . . . . . . . . . . . . . . . 552
9.9 ARef.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553
9.10 ArityException.java . . . . . . . . . . . . . . . . . . . . . . . 556
9.11 ArrayChunk.java . . . . . . . . . . . . . . . . . . . . . . . . . 556
9.12 ArraySeq.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 558
9.13 ASeq.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571
9.14 Associative.java . . . . . . . . . . . . . . . . . . . . . . . . . . 576
9.15 Atom.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 577
9.16 ATransientMap.java . . . . . . . . . . . . . . . . . . . . . . . 579
9.17 ATransientSet.java . . . . . . . . . . . . . . . . . . . . . . . . 581
9.18 BigInt.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 582
9.19 Binding.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585
9.20 Box.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585
9.21 ChunkBuffer.java . . . . . . . . . . . . . . . . . . . . . . . . . 586
9.22 ChunkedCons.java . . . . . . . . . . . . . . . . . . . . . . . . 586
9.23 Compile.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 588
9.24 Compiler.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 590
9.25 Cons.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766
9.26 Counted.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 768
9.27 Delay.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 768
9.28 DynamicClassLoader.java . . . . . . . . . . . . . . . . . . . . 769
9.29 EnumerationSeq.java . . . . . . . . . . . . . . . . . . . . . . . 770
9.30 Fn.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
9.31 IChunk.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
9.32 IChunkedSeq.java . . . . . . . . . . . . . . . . . . . . . . . . . 773
9.33 IDeref.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773
9.34 IEditableCollection.java . . . . . . . . . . . . . . . . . . . . . 774
9.35 IFn.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 774
9.36 IKeywordLookup.java . . . . . . . . . . . . . . . . . . . . . . 796
9.37 ILookup.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 797
9.38 ILookupSite.java . . . . . . . . . . . . . . . . . . . . . . . . . 797
9.39 ILookupThunk.java . . . . . . . . . . . . . . . . . . . . . . . . 798
9.40 IMapEntry.java . . . . . . . . . . . . . . . . . . . . . . . . . . 798
vi CONTENTS
10 jvm/clojure 1167
10.1 main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1167
11 clj/clojure/ 1169
11.1 core.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1169
11.2 protocols.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1296
11.3 coredeftype.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1298
11.4 coreprint.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1315
11.5 coreproxy.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1323
11.6 data.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1332
11.7 genclass.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1334
11.8 gvec.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1351
11.9 inspector.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1361
11.10browse.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1365
11.11browseui.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1366
11.12io.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1367
11.13javadoc.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1376
11.14shell.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1378
11.15main.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1381
11.16parallel.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1389
11.17clformat.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1394
11.18columnwriter.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1436
11.19dispatch.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1438
viii CONTENTS
11.20pprintbase.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1448
11.21prettywriter.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1456
11.22printtable.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1467
11.23utilities.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1467
11.24pprint.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1470
11.24.1 java.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1471
11.25reflect.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1476
11.26repl.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1479
11.27set.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1485
11.28stacktrace.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1489
11.29string.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1490
11.30template.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1496
11.31junit.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1497
11.32tap.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1501
11.33test.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1503
11.34version.properties . . . . . . . . . . . . . . . . . . . . . . . . . 1519
11.35walk.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1519
11.36xml.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1522
11.37zip.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1524
11.38pom-template.xml . . . . . . . . . . . . . . . . . . . . . . . . 1531
12 test/clojure 1533
12.1 test/testclojure.clj . . . . . . . . . . . . . . . . . . . . . . . . 1533
12.2 test/testhelper.clj . . . . . . . . . . . . . . . . . . . . . . . . 1535
12.3 test/agents.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1537
12.4 test/annotations.clj . . . . . . . . . . . . . . . . . . . . . . . . 1541
12.5 test/atoms.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1541
12.6 test/clojureset.clj . . . . . . . . . . . . . . . . . . . . . . . . 1542
12.7 test/clojurexml.clj . . . . . . . . . . . . . . . . . . . . . . . . 1546
12.8 test/clojurezip.clj . . . . . . . . . . . . . . . . . . . . . . . . 1547
12.9 test/compilation.clj . . . . . . . . . . . . . . . . . . . . . . . . 1548
12.10test/control.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1550
12.11test/data.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1556
12.12test/datastructures.clj . . . . . . . . . . . . . . . . . . . . . . 1557
12.13test/def.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1574
12.14test/errors.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1575
12.15test/evaluation.clj . . . . . . . . . . . . . . . . . . . . . . . . 1576
12.16test/for.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1580
12.17test/genclass.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1583
12.18test/javainterop.clj . . . . . . . . . . . . . . . . . . . . . . . . 1585
12.19test/keywords.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1594
12.20test/logic.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1594
12.21test/macros.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1599
12.22test/main.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1599
12.23test/metadata.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1600
12.24test/multimethods.clj . . . . . . . . . . . . . . . . . . . . . . 1602
CONTENTS ix
12.25test/nslibs.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1605
12.26test/numbers.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1607
12.27test/otherfunctions.clj . . . . . . . . . . . . . . . . . . . . . . 1618
12.28test/parallel.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1620
12.29test/pprint.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1621
12.30test/predicates.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1621
12.31test/printer.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1624
12.32test/protocols.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1626
12.33test/reader.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1633
12.34test/reflect.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1640
12.35test/refs.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1640
12.36test/repl.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1641
12.37test/rt.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1642
12.38test/sequences.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1644
12.39test/serialization.clj . . . . . . . . . . . . . . . . . . . . . . . 1668
12.40test/special.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1671
12.41test/string.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1672
12.42test/test.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1674
12.43test/testfixtures.clj . . . . . . . . . . . . . . . . . . . . . . . . 1677
12.44test/transients.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1678
12.45test/vars.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1678
12.46test/vectors.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1680
12.47test/java5.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . 1686
12.48test/java6andlater.clj . . . . . . . . . . . . . . . . . . . . . . 1688
12.49test/examples.clj . . . . . . . . . . . . . . . . . . . . . . . . . 1690
12.50test/io.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1691
12.51test/javadoc.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1696
12.52test/shell.clj . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1696
12.53test/testclformat.clj . . . . . . . . . . . . . . . . . . . . . . . 1697
12.54test1/testhelper.clj . . . . . . . . . . . . . . . . . . . . . . . . 1714
12.55test/testpretty.clj . . . . . . . . . . . . . . . . . . . . . . . . 1715
12.56test1/examples.clj . . . . . . . . . . . . . . . . . . . . . . . . 1721
12.57test/moreexamples.clj . . . . . . . . . . . . . . . . . . . . . . 1722
12.58test/example.clj . . . . . . . . . . . . . . . . . . . . . . . . . . 1722
Bibliography 1753
Index 1755
CONTENTS xi
Foreword
Rich Hickey invented Clojure. This is a fork of the project to experiment with
literate programming as a development and documentation technology.
Every effort is made to give credit for any and all contributions.
Clojure is a break with the past traditions of Lisp. This literate fork is a
break with the past traditions of code development. As such it is intended as
an experiment, not a replacement or competition with the official version of
Clojure.
Most programmers are still locked into the idea of making a program out of a
large pile of tiny files containing pieces of programs. They do not realize that
this organization was forced by the fact that machines like the PDP 11 only
had 8k of memory and a limit of 4k buffers in the editor. Thus there was a lot
of machinery built up, such as overlay linkers, to try to reconstruct the whole
program.
The time has come to move into a more rational means of creating and main-
taining programs. Knuth suggested we write programs like we write literature,
with the idea that we are trying to communicate the ideas to other people. The
fact that the machine can also run the programs is a useful side-effect but not
important.
Very few people have seen a literate program so this is intended as a complete
working example, published in book form. The intent is that you can sit and
read this book like any other novel. At the end of it you will be familiar with
the ideas and how those ideas are actually expressed in the code.
If programmers can read it and understand it then they can maintain and modify
it. The ideas will have been communicated. The code will be changed to match
changes in the idea. We will all benefit.
Ive tried to make it as simple as possible. Try it once, you might like it.
Tim Daly
December 28, 2010 ((iHy))
CONTENTS i
Step 3 Run tangle to extract the [1737] Makefile from this document.
Step 4
make
This will
create a new subdirectory called tpd containing all of the source code
test Clojure
create a running copy of Clojure
create the pdf
start a Clojure REPL
rm -rf tpd
tangle clojure.pamphlet Makefile >Makefile
make
This will destroy the old source, extract the Makefile, and rebuild the system.
On a fast processor this takes about a minute.
Resist the urge to edit the files in the tpd directory. They are only there for the
compiler. Edit this file directly.
You can change where Clojure is built. In the [1737] Makefile there is a line
which defines the root of the directory build. You can change this or override
it on the command line to build Clojure elsewhere.
WHERE=tpd
To build a second copy of Clojure, or to work in some other directory, just type
make WHERE=newplace
Why Bother?
Why bother with such a difficult method of programming? Because worthwhile
programs should live.
Programs live because people maintain them. Maintaining and modifying
code correctly requires that you understand why the program is written as it
is, what the key ideas that make the program work, and why certain, not very
obvious, pieces of code exist. Programmers almost never write this information
down anywhere. Great ideas are invented, the code is written, a man page of
documentation is created, and the job is done.
Well, almost. What does is mean for a program to live? How does a program
survive once the original developers leave the project? There are many sources
of information but almost no source of knowledge. New programmers dont
know what the elders know. In order to live and continue to grow there
has to be a way to transmit this knowledge.
Literate programming is Knuths proposed idea for moving from the world of
ideas to the world of information. This is not simply another documentation
format. This is meant to be Literature. The ideas are presented, the impli-
cations are explored, the tradeoffs are discussed, and the code is motivated,
like characters in a novel.
You are encouraged to write or rewrite sections of this document to improve the
communication with the readers.
But I have to learn latex!. Well, for this document you do. But LATEXis
just more than a document markup language like HTML and it is no harder
to learn. It gives you the added advantage that you have a real language for
publishing real documents. Most books are typeset with this technology and
a lot of conferences and Journals require it. If you can learn Clojure, you can
learn LATEX. If youre a programmer you will always need to continue to learn,
at least until you retire into management.
CONTENTS iii
Having used literate programming for years I have collected some key quotes
that might stimulate your interest.
I believe that the time is ripe for significantly better documentation of pro-
grams, and that we can best achieve this by considering programs to be works
of literature. Hence, my title Literate Programming. Let us change our
traditional attitude to the construction of programs. Instead of imagining
that our main task is to instruct a computer what to do, let us concentrate
on explaining to human beings what we want a computer to do.
Donald Knuth Literate Programming (1984)
Step away from the machine. Literate programming has nothing to do with
tools or style. It has very little to do with programming. One of the hard
transitions to literate programming is literate thinking.
Timothy Daly in Lambda the Ultimate (2010)
The conversation is much more direct if the Design Concept per se, rather
than derivative representatives or partial details, is the focus.
Fred Brooks, The Design of Design
We are banning the old notion of literate programming that I used when
developing TEX82 because documentation has proven to be too much of a
pain.
Donald Knuth TUG 2010
iv CONTENTS
Once upon a time I took great care to ensure that TEX82 would be truly
archival so that results obtainable today would produce the same output 50
years from now but that was manifestly foolish. Lets face it, who is going
to care one whit for what I do today after even 5 years have elapsed, let
alone 50. Life is too short to re-read anything anymore in the internet age.
Nothing over 30 months old is trustworthy or interesting.
Donald Knuth TUG 2010
Chapter 1
mkdir -p ~/bin
cd ~/bin
wget https://raw.github.com/technomancy/leiningen/stable/bin/lein
chmod a+x lein
cd
lein new scratch
This creates a new directory in your homedir, called scratch. If you see command
not found instead, it means the directory /bin isnt registered with your terminal
as a place to search for programs. To fix this, add the line
1
2 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
export PATH="$PATH":~/bin
source ~/.bash_profile.
Re-running
should work.
Lets enter that directory, and start using Clojure itself:
cd scratch
lein repl
user=> nil
nil
user=> true
true
user=> false
false
1.1. CLOJURE FROM THE GROUND UP 3
true and false are a pair of special values called Booleans. They mean exactly
what you think: whether a statement is true or false. true, false, and nil form
the three poles of the Lisp logical system.
user=> 0
0
This is the number zero. Its numeric friends are 1, -47, 1.2e-4, 1/3, and so
on. We might also talk about strings, which are chunks of text surrounded by
double quotes:
nil, true, 0, and hi there! are all different types of values; the nouns of pro-
gramming. Just as one could say House. in English, we can write a program
like hello, world and it evaluates to itself: the string hello world. But most
sentences arent just about stating the existence of a thing; they involve action.
We need verbs.
user=> inc
#<core$inc clojure.core$inc@6f7ef41c>
This is a verb called incshort for increment. Specifically, inc is a symbol which
points to a verb: #<core$inc clojure.core$inc@6f7ef41c> just like the word run
is a name for the concept of running.
Theres a key distinction herethat a signifier, a reference, a label, is not the same
as the signified, the referent, the concept itself. If you write the word run on
paper, the ink means nothing by itself. Its just a symbol. But in the mind of a
reader, that symbol takes on meaning; the idea of running.
Unlike the number 0, or the string hi, symbols are references to other values.
when Clojure evaluates a symbol, it looks up that symbols meaning. Look up
inc, and you get #<core$inc clojure.core$inc@6f7ef41c>.
Can we refer to the symbol itself, without looking up its meaning?
user=> inc
inc
Yes. The single quote escapes an expression. It says Rather than evaluating
this text, simply return the text itself, unchanged. Quote a symbol, get a
symbol. Quote a number, get a number. Quote anything, and get it back
exactly as it came in.
user=> 123
4 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
123
user=> "foo"
"foo"
user=> (1 2 3)
(1 2 3)
A new kind of value, surrounded by parentheses: the list. LISP originally stood
for LISt Processing, and lists are still at the core of the language. This list
contains three elements: the numbers 1, 2, and 3. Lists can contain anything:
numbers, strings, even other lists:
A list containing two elements: the number 1, and a second list. That list
contains two elements: the number 2, and another list. That list contains two
elements: 3, and an empty list.
user=> (1 (2 (3 ())))
(1 (2 (3)))
Took
Lindsay
my best friend
the dog
which we found together
at the pound
on fourth street
for a walk
with her mother
Michelle
But lets try something simpler. Something we know how to talk about. Incre-
ment the number zero. As a tree:
Increment
the number zero
We have a symbol for incrementing, and we know how to write the number zero.
Lets combine them in a list:
1.1. CLOJURE FROM THE GROUND UP 5
clj=> (inc 0)
(inc 0)
A basic sentence. Remember, since its quoted, were talking about the tree,
the text, the expression, by itself. Absent interpretation. If we remove the
single-quote, Clojure will interpret the expression:
user=> (inc 0)
1
Incrementing zero yields one. And if we wanted to increment that value? In-
crement increment the number zero
Clojure first looks up the meanings for the symbols in the code:
(#<core$inc clojure.core$inc@6f7ef41c>
(#<core$inc clojure.core$inc@6f7ef41c>
0))
Then evaluates the innermost list (inc 0), which becomes the number 1:
(#<core$inc clojure.core$inc@6f7ef41c>
1)
Every list starts with a verb. Parts of a list are evaluated from left to right.
Innermost lists are evaluated before outer lists.
(+ 1 (- 5 2) (+ 3 4))
(+ 1 3 (+ 3 4))
(+ 1 3 7)
11
6 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
Thats it.
The entire grammar of Lisp: the structure for every expression in the language.
We transform expressions by substituting meanings for symbols, and obtain
some result. This is the core of the Lambda Calculus, and it is the theoretical
basis for almost all computer languages. Ruby, Javascript, C, Haskell; all lan-
guages express the text of their programs in different ways, but internally all
construct a tree of expressions. Lisp simply makes it explicit.
1.1.3 Review
We started by learning a few basic nouns: numbers like 5, strings like cat, and
symbols like inc and +. We saw how quoting makes the difference between an
expression itself and the thing it evaluates to. We discovered symbols as names
for other values, just like how words represent concepts in any other language.
Finally, we combined lists to make trees, and used those trees to represent a
program.
With these basic elements of syntax in place, its time to expand our vocabu-
lary with new verbs and nouns; learning to represent more complex values and
transform them in different ways.
1.2.1 Types
Weve seen a few different values alreadyfor instance, nil, true, false, 1, 2.34, and
meow. Clearly all these things are different values, but some of them seem
more alike than others.
For instance, 1 and 2 are very similar numbers; both can be added, divided,
multiplied, and subtracted. 2.34 is also a number, and acts very much like 1
and 2, but its not quite the same. Its got decimal points. Its not an integer.
And clearly true is not very much like a number. What is true plus one? Or
false divided by 5.3? These questions are poorly defined.
We say that a type is a group of values which work in the same way. Its a
property that some values share, which allows us to organize the world into sets
of similar things. 1 + 1 and 1 + 2 use the same addition, which adds together
integers. Types also help us verify that a program makes sense: that you can
only add together numbers, instead of adding numbers to porcupines.
Types can overlap and intersect each other. Cats are animals, and cats are fuzzy
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 7
too. You could say that a cat is a member (or sometimes instance), of the fuzzy
and animal types. But there are fuzzy things like moss which arent animals,
and animals like alligators that arent fuzzy in the slightest.
Other types completely subsume one another. All tabbies are housecats, and
all housecats are felidae, and all felidae are animals. Everything which is true of
an animal is automatically true of a housecat. Hierarchical types make it easier
to write programs which dont need to know all the specifics of every value; and
conversely, to create new types in terms of others. But they can also get in the
way of the programmer, because not every useful classification (like fuzziness) is
purely hierarchical. Expressing overlapping types in a hierarchy can be tricky.
Every language has a type system; a particular way of organizing nouns into
types, figuring out which verbs make sense on which types, and relating types
to one another. Some languages are strict, and others more relaxed. Some
emphasize hierarchy, and others a more ad-hoc view of the world. We call
Clojures type system strong in that operations on improper types are simply
not allowed: the program will explode if asked to subtract a dandelion. We
also say that Clojures types are dynamic because they are enforced when the
program is run, instead of when the program is first read by the computer.
Well learn more about the formal relationships between types later, but for now,
keep this in the back of your head. Itll start to hook in to other concepts later.
1.2.2 Integers
Lets find the type of the number 3:
user=> (type 3)
java.lang.Long
user=> Long/MAX_VALUE
9223372036854775807
8 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
Thats a reasonably big number. Most of the time, you wont need anything
bigger, but what if you did? What happens if you add one to the biggest Long?
An error occurs! This is Clojure telling us that something went wrong. The
type of error was an ArithmeticException, and its message was integer overflow,
meaning this type of number cant hold a number that big. The error came from
a specific place in the source code of the program: Numbers.java, on line 1388.
Thats a part of the Clojure source code. Later, well learn more about how to
unravel error messages and find out what went wrong.
The important thing is that Clojures type system protected us from doing some-
thing dangerous; instead of returning a corrupt value, it aborted evaluation and
returned an error.
If you do need to talk about really big numbers, you can use a BigInt: an
arbitrary-precision integer. Lets convert the biggest Long into a BigInt, then
increment it:
Notice the N at the end? Thats how Clojure writes arbitrary-precision integers.
Integers are half the size of Longs; they store values in 32 bits. Shorts are 16
bits, and Bytes are 8. That means their biggest values are 2311 , 2151 , and
271 , respectively.
user=> Integer/MAX_VALUE
2147483647
user=> Short/MAX_VALUE
32767
user=> Byte/MAX_VALUE
127
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 9
Floating point math is complicated, and we wont get bogged down in the details
just yet. The important thing to know is floats and doubles are approximations.
There are limits to their correctness:
user=> 0.99999999999999999
1.0
user=> (+ 1 2)
3
user=> (+ 1 2.0)
3.0
3 and 3.0 are not the same number; one is a long, and the other a double. But
for most purposes, theyre equivalent, and Clojure will tell you so:
user=> (= 3 3.0)
false
user=> (== 3 3.0)
true
= asks whether all the things that follow are equal. Since floats are approxi-
mations, = considers them different from integers. == also compares things,
10 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
user=> (- 3 1)
2
user=> (* 1.5 3)
4.5
user=> (/ 1 2)
1/2
Putting the verb first in each list allows us to add or multiply more than one
number in the same step:
user=> (+ 1 2 3)
6
user=> (* 2 3 1/5)
6/5
Subtraction with more than 2 numbers subtracts all later numbers from the
first. Division divides the first number by all the rest.
user=> (- 5 1 1 1)
2
user=> (/ 24 2 3)
4
user=> (+ 2)
2
user=> (- 2)
-2
user=> (* 4)
4
user=> (/ 4)
1/4
We can also add or multiply a list of no numbers at all, obtaining the addi-
tive and multiplicative identities, respectively. This might seem odd, especially
coming from other languages, but well see later that these generalizations make
it easier to reason about higher-level numeric operations.
user=> (+)
0
user=> (*)
1
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 11
Often, we want to ask which number is bigger, or if one number falls between
two others. <= means less than or equal to, and asserts that all following values
are in order from smallest to biggest.
user=> (<= 1 2 3)
true
user=> (<= 1 3 2)
false
< means strictly less than, and works just like <=, except that no two values
may be equal.
user=> (<= 1 1 2)
true
user=> (< 1 1 2)
false
Their friends > and >= mean greater than and greater than or equal to, re-
spectively, and assert that numbers are in descending order.
user=> (> 3 2 1)
true
user=> (> 1 2 3)
false
Also commonly used are inc and dec, which add and subtract one to a number,
respectively:
user=> (inc 5)
6
user=> (dec 5)
4
One final note: equality tests can take more than 2 numbers as well.
user=> (= 2 2 2)
true
user=> (= 2 2 3)
false
1.2.5 Strings
We saw that strings are text, surrounded by double quotes, like foo. Strings
in Clojure are, like Longs, Doubles, and company, backed by a Java type:
We can make almost anything into a string with str. Strings, symbols, numbers,
booleans; every value in Clojure has a string representation. Note that nils string
representation is ; an empty string.
str can also combine things together into a single string, which we call concate-
nation.
To look for patterns in text, we can use a regular expression, which is a tiny
language for describing particular arrangements of text. re-find and re-matches
look for occurrences of a regular expression in a string. To find a cat:
Regular expressions are a powerful tool for searching and matching text, es-
pecially when working with data files. Since regexes work the same in most
languages, you can use any guide online to learn more. Its not something you
have to master right away; just learn specific tricks as you find you need them.
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 13
user=> (boolean 0)
true
user=> (boolean 1)
true
user=> (boolean "hi there")
true
user=> (boolean str)
true
user=> (not 2)
false
user=> (not nil)
true
Well learn more about Boolean logic when we start talking about control flow;
the way we alter evaluation of a program and express ideas like if Im a cat, then
meow incessantly.
1.2.7 Symbols
We saw symbols in the previous chapter; theyre bare strings of characters, like
foo or +.
Every symbol actually has two names: one, a short name, is used to refer
to things locally. Another is the fully qualified name, which is used to refer
unambiguously to a symbol from anywhere. If I were a symbol, my name would
be Kyle, and my full name Kyle Kingsbury.
Symbol names are separated with a /. For instance, the symbol str actually
comes from a family called clojure.core, which means that its full name is clo-
jure.core/str
When we talked about the maximum size of an integer, that was a fully-qualified
symbol, too.
The job of symbols is to refer to things, to point to other values. When eval-
uating a program, symbols are looked up and replaced by their corresponding
values. Thats not the only use of symbols, but its the most common.
1.2.8 Keywords
Closely related to symbols and strings are keywords, which begin with a :.
Keywords are like strings in that theyre made up of text, but are specifically
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 15
intended for use as labels or identifiers. These arent labels in the sense of
symbols: keywords arent replaced by any other value. Theyre just names, by
themselves.
As labels, keywords are most useful when paired with other values in a collection,
like a map. Well come back to keywords shortly.
1.2.9 Lists
A collection is a group of values. Its a container which provides some structure,
some framework, for the things that it holds. We say that a collection con-
tains elements, or members. We saw one kind of collectiona listin the previous
chapter.
user=> (1 2 3)
(1 2 3)
user=> (type (1 2 3))
clojure.lang.PersistentList
Remember, we quote lists with a to prevent them from being evaluated. You
can also construct a list using list:
user=> (list 1 2 3)
(1 2 3)
user=> (conj (1 2 3) 4)
(4 1 2 3)
We added 4 to the listbut it appeared at the front. Why? Internally, lists are
stored as a chain of values: each link in the chain is a tiny box which holds the
value and a connection to the next link. This data structure, called a linked
list, offers immediate access to the first element.
16 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
But getting to the second element requires an extra hop down the chain
nth gets the element of an ordered collection at a particular index. The first
element is index 0, the second is index 1, and so on.
This means that lists are well-suited for small collections, or collections which
are read in linear order, but are slow when you want to get arbitrary elements
from later in the list. For fast access to every element, we use a vector.
1.2.10 Vectors
Vectors are surrounded by square brackets, just like lists are surrounded by
parentheses. Because vectors arent evaluated like lists are, theres no need to
quote them:
user=> [1 2 3]
[1 2 3]
user=> (type [1 2 3])
clojure.lang.PersistentVector
You can also create vectors with vector, or change other structures into vectors
with vec:
user=> (vector 1 2 3)
[1 2 3]
user=> (vec (list 1 2 3))
[1 2 3]
user=> (conj [1 2 3] 4) [1 2 3 4]
Our friends first, second, and nth work here too; but unlike lists, nth is fast on
vectors. Thats because internally, vectors are represented as a very broad tree
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 17
of elements, where each part of the tree branches into 32 smaller trees. Even
very large vectors are only a few layers deep, which means getting to elements
only takes a few hops.
In addition to first, youll often want to get the remaining elements in a collection.
There are two ways to do this:
rest and next both return everything but the first element. They differ only
by what happens when there are no remaining elements:
rest returns logical true, next returns logical false. Each has their uses, but
in almost every case theyre equivalentI interchange them freely.
We can get the final element of any collection with last:
Because vectors are intended for looking up elements by index, we can also use
them directly as verbs:
So we took the vector containing three keywords, and asked Whats the element
at index 1? Index 1 is the second element, so this evaluates to :b.
Finally, note that vectors and lists containing the same elements are considered
equal in Clojure:
user=> (= (1 2 3) [1 2 3])
true
In almost all contexts, you can consider vectors, lists, and other sequences as
interchangeable. They only differ in their performance characteristics, and in a
few data-structure-specific operations.
18 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
1.2.11 Sets
Sometimes you want an unordered collection of values; especially when you plan
to ask questions like does the collection have the number 3 in it? Clojure, like
most languages, calls these collections sets.
Sets are surrounded by #{...}. Notice that though we gave the elements :a, :b,
and :c, they came out in a different order. In general, the order of sets can shift
at any time. If you want a particular order, you can ask for it as a list or vector:
Sets never contain an element more than once, so conjing an element which is
already present does nothing. Conversely, one removes elements with disj:
The most common operation with a set is to check whether something is inside
it. For this we use contains?.
Like vectors, you can use the set itself as a verb. Unlike contains?, this expression
returns the element itself (if it was present), or nil.
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 19
user=> (#{1 2 3} 3)
3
user=> (#{1 2 3} 4)
nil
You can make a set out of any other collection with set.
1.2.12 Maps
The last collection on our tour is the map: a data structure which associates
keys with values. In a dictionary, the keys are words and the definitions are
the values. In a library, keys are call signs, and the books are values. Maps
are indexes for looking things up, and for representing different pieces of named
information together.
Maps are surrounded by braces {...}, filled by alternating keys and values. In
this map, the three keys are :name, :color, and :weight, and their values are
spook, black, and 2, respectively. We can look up the corresponding value
for a key with get:
get can also take a default value to return instead of nil, if the key doesnt exist
in that map.
Since lookups are so important for maps, we can use a map as a verb directly:
And conversely, keywords can also be used as verbs, which look themselves up
in maps:
20 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
You can add a value for a given key to a map with assoc.
assoc adds keys if they arent present, and replaces values if theyre already
there. If you associate a value onto nil, it creates a new map.
You can combine maps together using merge, which yields a map containing
all the elements of all given maps, preferring the values from later ones.
Or a recipe:
1.2. CLOJURE FROM THE GROUND UP: BASIC TYPES 21
What is this type thing, exactly? What are these verbs weve been learning,
and where do they come from? This is the central question of chapter three:
functions.
22 CHAPTER 1. FROM THE GROUND UP (KYLE KINGSBURY)
Chapter 2
From ideas to
implementation
23
24 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
they work, and the general idea behind the vector implementation.
The Basic Idea
Mutable vectors and ArrayLists are generally just arrays which grows and
shrinks when needed. This works great when you want mutability, but is a
big problem when you want persistence. You get slow modification operations
because youll have to copy the whole array all the time, and it will use a lot of
memory. It would be ideal to somehow avoid redundancy as much as possible
without losing performance when looking up values, along with fast operations.
That is exactly what Clojures persistent vector does, and it is done through
balanced, ordered trees.
The idea is to implement a structure which is similar to a binary tree. The
only difference is that the interior nodes in the tree have a reference to at most
two subnodes, and does not contain any elements themselves. The leaf nodes
contain at most two elements. The elements are in order, which means that the
first element is the first element in the leftmost leaf, and the last element is the
rightmost element in the rightmost leaf. For now, we require that all leaf nodes
are at the same depth. As an example, take a look at the tree below: It has the
integers 0 to 8 in it, where 0 is the first element and 8 the last. The number 9
is the vector size:
vector size 9
vector pointer
node
leaf node
element
0 1 2 3 4 5 6 7 8
Visualization of a vector with 9 elements in it.
If we wanted to add a new element to the end of this vector and we were in the
mutable world, we would insert 9 in the rightmost leaf node:
2.3. UNDERSTANDING PERSISTENT VECTORS (JEAN LORANGE) 25
10
0 1 2 3 4 5 6 7 8 9
Visualization of a vector with 10 elements in it.
But heres the issue: We cannot do that if we want to be persistent. And this
would obviously not work if we wanted to update an element! We would need
to copy the whole structure, or at least parts of it.
To minimize copying while retaining full persistence, we perform path copying:
We copy all nodes on the path down to the value were about to update or
insert, and replace the value with the new one when were at the bottom. A
result of multiple insertions is shown below. Here, the vector with 7 elements
share structure with a vector with 10 elements:
10
0 1 2 3 4 5 6 6 7 8 9
A visualization of two vectors, which use structural sharing.
The pink coloured nodes and edges are shared between the vectors, whereas the
brown and blue are separate. Other vectors not visualized may also share nodes
with these vectors. Update
The easiest modification operator to understand would be updates/replacing
values in a vector, so we will explain how updating works first. In Clojure,
thats an assoc modification, or an update-in.
To update an element, we would have to walk the tree down to the leaf node
where the element is place. While we walk down, we copy the nodes on our path
to ensure persistence. When weve gotten down to the leaf node, we copy it and
26 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
replace the value we wanted to replace with the new value. We then return the
new vector with the modified path.
As an example, lets assume we perform an assoc on the vector with the elements
from 0 to 8, like this:
The internal structures, where the blue one has the copied path, is shown below:
9 9
0 1 2 3 4 5 4 beef 6 7 8
Two vectors, where weve updated a value.
Given that we have a way to know which node to go down, this seems easy
enough. Well go through how we find the path to a specific index in a later
part of this series.
2.3.1 Insertion
Insertion is not too much different from an update, except that we have some
edge cases where we have to generate nodes in order to fit in a value. We
essentially have three cases:
There is room for a new value in the rightmost leaf node. There is space in
the root, but not in the rightmost leaf node. There is not enough space in the
current root.
Well go through them all, as their solutions are not that difficult to grasp.
Whenever there is enough space in the rightmost leaf node, we can just do as we
do when we perform an assoc: We just copy the path, and at the newly created
leaf node, we put in the value to the right of the rightmost element.
As an example, heres how we would do (conj [0 1 2 3 4] 5), and the internal
structures from doing so. black is the old:
2.3. UNDERSTANDING PERSISTENT VECTORS (JEAN LORANGE) 27
5 6
0 1 2 3 4 4 5
Insertion with enough space in the leaf node.
Thats it. Theres no magic, just path copying and insertion in the leaf node.
So, what do we do when theres not enough space in the leftmost leaf node?
Luckily, well never end up in a position where we find out that were in the
wrong leaf node: We will always take the right path down to the leaf node.
Instead, we will realize that the node were trying to go down to doesnt yet
exist (the pointer is null). When a node doesnt exist, we generate one and set
that one as the copied node.
12 13
0 1 2 3 4 5 6 7 8 9 a b c
Root Overflow
The last case is the root overflow case. This happens when there isnt more
space in the tree with the current root node.
Its not that difficult to understand how we would solve this: We make a new
root node, and set the old root as the first child of the new root. From there
on, we perform the node generating, just as we did in the previous solution.
28 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
0 1 2 3 4 5 6 7 8
Insertion where we generate a new root.
One thing is to solve the problem, but detecting when it happens is also im-
portant. Luckily, this is also rather easy. When we have a two-way branching
vector, this happens when the old vectors size is a power of two. Generally
speaking, an n-way branching vector will have an overflow when the size is a
power of n.
2.3.2 Popping
The solutions for popping (removing the last element) isnt that difficult to
grasp either. Popping similar to inserting in that there are three cases:
The rightmost leaf node contains more than one element. The rightmost leaf
node contains exactly one element (zero after popping). The root node contains
exactly one element after popping.
Essentially, these are all ways of reverting 1, 2 and 3 in the previous section,
neither of which are extremely complex.
6 5
0 1 2 3 4 5 4
2.3. UNDERSTANDING PERSISTENT VECTORS (JEAN LORANGE) 29
Popping a value from a vector with more than one element in the
rightmost leaf node.
Keep in mind that popping multiple times on a vector will not yield identical
vectors: They are equal, but they dont share the root. For instance,
6 5 4
0 1 2 3 4 5 4 5
Performing pops on the same vector.
Whenever we have a leaf node with only a single node, we have a different case.
We would like to avoid empty nodes in our tree at all cost. Therefore, whenever
we have an empty node, instead of returning it, we return null instead. The
parent node will then contain a null pointer, instead of a pointer to an empty
node:
6 7
0 1 2 3 4 5 6
Popping and removing a leaf node.
Here, the brown vector is the original, whereas the blue one is the popped one.
Unfortunately, it is not as easy as to just remove leaf nodes. You see, if we
return a null pointer to a node, which originally only had one child, we must
convert that one into a null pointer which we send back: The results of emptying
30 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
a node propagates upwards. This is a bit tricky to get right, but essentially it
works by looking at the new child, check if it is null and is supposed to be placed
at index 0, and return null if thats the case.
If this was implemented in Clojure, it may look something like this recursive
function:
When such a function has been implemented, node removal has been taken care
of completely. As an example, see the graph below. Here, the popped (blue)
vector has removed two nodes: The leaf node containing c and its parent.
13 12
0 1 2 3 4 5 6 7 8 9 10 11 12
Popping and removing multiple nodes.
Root Killing
We have now covered all cases, except for one. With the current implementation,
we would get the following result if we popped a vector with nine elements:
2.3. UNDERSTANDING PERSISTENT VECTORS (JEAN LORANGE) 31
8 9
0 1 2 3 4 5 6 7 8
Popping with bad root handling.
Thats right, wed have a root with a single pointer to a child node. Pretty
useless, as we would always move down into the child when we lookup or assoc
values, and inserting values would create a new root. What we would like to do
is to get rid of it.
This is possibly the easiest thing in this blogpost to actually do: After we have
finished popping, check if the root node contains only a single child (check that
the second child is null, for instance). If thats the case, and the root node is
not a leaf node, we can just replace the root node with its child.
The result is, as expected, a new vector (blue) with the first child of the original
vectors root node as root:
0 1 2 3 4 5 6 7 8
Popping with proper root handling.
To actually see the difference: Here is a 4-way branching tree with 14 elements,
which only is 2 levels deep. If you scroll up a bit, youll see a figure with two
vectors containing 13 and 12 elements, respectively. With two-way branching,
this is already 4 levels deep, double the height as this one.
14
0 1 2 3 4 5 6 7 8 9 a b c d
A 4-way branching vector.
2.3.4 Persistence
In the last post, I used the word persistent. I said we want to be persistent,
but didnt really explain what persistence itself really means.
A persistent data structure doesnt modify itself: Strictly speaking they dont
have to be immutable internally, just have to be perceived as such. Whenever
you do updates, inserts and removals on a persistent data structure, you
get a new data structure back. The old version will always be consistent, and
whenever given some input in, it will always spit out the same output.
When we talk about a fully persistent data structure, all versions of a structure
should be updateable, meaning that all possible operations you can do on a
version can be performed on another. In early functional data structure time,
2.3. UNDERSTANDING PERSISTENT VECTORS (JEAN LORANGE) 33
it was common to cheat with the structures and make the older versions
decay over time by mutating the internals, making them slower and slower
compared to the newer versions. However, Rich Hickey decided that all the
versions of Clojures persistent structures should have the same performance
guarantees, regardless of which version of the structure you are using.
Vector
A vector is a one-dimensional growable array. C++s std::vector and Javas
java.util.ArrayList are examples of mutable implementations. Theres not much
more to it than that, really. A vector trie is a trie which represents a vector. It
doesnt have to be persistent, but in our case, it is.
Trie
Tries are a specific type of trees, and I think its best to show the actual difference
by explaining the more known trees first.
In RB-trees and most other binary trees, mappings or elements are contained
in the interior nodes. Picking the right branch is done by comparing the ele-
ment/key at the current node: If the element is lower than the node element,
we branch left, and if it is higher, we branch right. Leaves are usually null
pointers/nil, and doesnt contain anything.
13
8 17
1 11 15 25
nil 6 22 27
nil nil nil nil
A RB-tree
The RB-tree above is taken from Wikipedias article on RB-trees. Im not going
to explain how those work in detail, but let us take a tiny example on how we
check if 22 is contained in the RB-tree:
A trie, on the other hand, has all the values stored in its leaves. Picking the
right branch is done by using parts of the key as a lookup. Consequently, a trie
may have more than two branches. In our case, we may have as many as 32!
a b
a c a b
5 7 8 3
A trie
An example of a general trie is illustrated in the figure above. That specific trie
is a map: It takes a string of length two, and returns an integer represented by
that string if it exists in the trie. ac has the value 7, whereas ba has the value
8. Heres how the trie works:
For strings, we split the string into characters. We then take the first character,
find the edge represented by this value, and walk down that edge. If there is
no edge for that value, we stop, as it is not contained in the trie. If not, we
continue with the second character, and so on. Finally, when we are done, we
return the value if it exists.
As an example, consider ac. We do as follows:
Clojures Persistent Vector is a trie where the indices of elements are used as
keys. But, as you may guess, we must split up the index integers in some way.
To split up integers, we either use digit partitioning or its faster sibling, bit
partitioning.
Digit Partitioning
Digit partitioning means that we split up the key into digits, which we then use
as a basis for populating a trie. For instance, we can split up the key 9128 to
[9, 1, 2, 8], and put an element into a trie based on that. We may have to pad
with zeroes at the front of the list, if the depth of the trie is larger than the size
of the list.
2.3. UNDERSTANDING PERSISTENT VECTORS (JEAN LORANGE) 35
We can also use whatever base we would like, not just base 10. We would then
have to convert the key to the base we wanted to use, and use the digits from
the conversion. As an example, consider 9128 yet again. 9128 is 35420 in base
7, so we would have to use the list [3, 5, 4, 2, 0] for lookup/insertion in the trie.
6
6 5 6
5 4 5
6 6 4 3 4
5 5 3 2 3
9457 4 4 2 1 2
3 3 1 0 1
2 2 0 0
1 1
0 0
Digit 3 5 4 2 0
Visualization of the 35420 lookup.
The trie (laying sideways, without the edges and nodes were not walking) above
shows how we traverse a digit-partitioned trie: We pick the most significant
digit, in this case 3, and walk that specific branch. We continue with the second
most significant digit in the same manner, until we have no digits left. When
weve walked the last branch, the object were standing with the first object in
the rightmost array in this case is the object we wanted to look up.
Implementing such a lookup scheme is not too hard, if you know how to find the
digits. Heres a Java version where everything not related to lookup is stripped
away:
The rDepth value represents the maximal size of a child of the root node: A
number with n digits will have n to the power of RADIX possible values, and
we must be able to put them all in the trie without having collisions.
In the for loop within the lookup method, the value size represents the maximal
size a child of the current node can have. For each child we go over, that size is
decremented by the branching factor, i.e. the radix or base of the digit trie.
The reason were performing a modulo operation on the result is to ignore the
more significant digits digits weve branched on earlier. We could potentially
remove the higher digit from the key every time we branch into a child, but the
code would be a tiny bit more complicated in that case. Bit Partitioning
Digit-partitioned tries would generally have to do a couple of integer divisions
and modulo operations. Doing this is on every branch we must take is a bit
time consuming. We would therefore like to speed this part up if it is possible.
So, as you may guess, bit-partitioned tries are a subset of the digit-partitioned
tries. All digit-partitioned tries in a base which is a power of two (2, 4, 8, 16,
32, etc) can be turned into bit-partitioned ones. With some knowledge of bit
manipulation, we can remove those costly arithmetic operations.
Conceptually, it works in the same way as digit partitioning does. However,
instead of splitting the key into digits, we split it into chunks of bits with some
predefined size. For 32-way branching tries, we need 5 bits in each part, and for
4-way branching tries, we need 2. In general, we need as many bits as the size
of our exponent.
So, why is this faster? By using bit tricks, we can get rid of both integer division
and modulo. If power is two to the power of n, we can use that
These formulas are just identities related to how integers are represented inter-
nally, namely as sequences of bits.
If we use this result and combine it with the previous implementation, we end
up with the following code:
This is more or less exactly what Clojures implementation is doing! See these
lines of the Clojure code to verify it; The only difference is that it performs
boundary checks and a tail check as well.
The important thing to note here is that weve not only changed the operators,
but weve also replaced the rDepth value with a shift value. Instead of storing
the whole value, were only storing the exponent. This makes us able to use
bitshifting on the key, which we use in the (key level) part. The other
parts should be fairly straightforward to understand, given that one knows bit
operations well. However, lets take an example for the ones unfamiliar with such
tricks. The explanation is quite thorough, so feel to skip parts you understand.
Say we have only have 2 bit partitioning (4-way branching) instead of 5 bits
(32-way) for visualization purposes. If we want to look up a value in a trie with
887 elements, we would have a shift equal to 8: All the children of the root node
can contain at most 1 8 == 256 elements each. The width and mask is also
changed by the bit count: The mask will here be 3 instead of 31.
3 3
3 2 3 2 3
887 2 1 2 1 2
1 0 1 0 1
0 0 0
level 8 6 4 2 0
index 2 1 3 0 2
0...0 10 01 11 00 10
Visualization of the 626 lookup.
Say we want to look up the contents of the element with key 626. 626 in its
38 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
binary representation is 0000 0010 0111 0010. Following the algorithm, step by
step, both written above and within Clojures source code, we would have to do
the following:
That is almost every single machine instruction you would have to perform to
lookup a value in a Clojure vector, with a depth of 5. Such a vector would
contain between 1 and 33 million elements. The fact that the shifts and masks
are some of the most efficient operations on a modern CPU makes the whole
deal even better. From a performance perspective, the only pain point left
on lookups are the cache misses. For Clojure, that is handled pretty well by the
JVM itself.
And thats how you do lookups in tries and in Clojures vector implementation.
I would guess the bit operations are the hardest one to grok, everything else
is actually very straightforward. You just need a rough understanding on how
tries work, and thats it!
2.4. RED BLACK TREES 39
2.4.1 Persistence
Driscoll defines a persistent data structure as one that supports mutliple ver-
sions. A data structure that allows only a single version at a time is called
ephemeral [DSST89].
The basic technique of creating a persistent data structure is to make copies.
However this is expensive in time and space. Driscoll shows that data structures
can be defined using links between nodes where the nodes carry information
and the links connect the nodes. In such structures it is possible to only copy
nodes that are changed and update the links. Using this technique does not
modify the old data structure so the links can share portions of the old structure.
Since the old structure is not changed it is also available at the same time as
the new structure, albeit with different root nodes.
We have functions to set the color, blacken and redden. We can ask this node
for its key and val
Since every node has to maintain the Red-Black balance we have two methods,
balanceLeft and balanceRight. Note that these methods always return a black
node as the root of the red-black tree is always black.
We also have a method to replace this node, the replace method.
Some of the methods are abstract which means that any class that implements
a Node has to implement the following methods:
Since the data structure is persistent we need to copy rather than modify the
structure. Each implementation of this abstract class has to handle the details.
Node assumes that it will return a Black node from its balance operations.
Nodes in a Clojure PersistentTreeMap have one of 8 possible subtypes of Node:
(AMapEntry [527])
PersistentTreeMap Node Class
Node(Object key){
this.key = key;
}
Node left(){
return null;
}
Node right(){
return null;
}
A Black node is a leaf node with a null value. This is constructed by Persis-
tentTreeMaps [45] black method.
(Node [41])
PersistentTreeMap Black Class
2.4. RED BLACK TREES 43
Node blacken(){
return this;
}
Node redden(){
return new Red(key);
}
Node redden(){
return new RedVal(key, val);
}
Interior nodes of Clojures Red Black trees can have a value. A BlackBranch
node is a leaf node with a null value and children, one of which could possibly
be null. This is constructed by PersistentTreeMaps [45] black method.
(Black [42])
PersistentTreeMap BlackBranch Class
static class BlackBranch extends Black{
final Node left;
Node redden(){
return new RedBranch(key, left, right);
}
A BlackBranchVal node is a leaf node with a value and children, one of which
2.4. RED BLACK TREES 45
Node redden(){
return new RedBranchVal(key, val, left, right);
}
If both of the children are null we must be constructing a leaf node. If the value
is null we need only construct a naked Black node, otherwise we construct a
BlackVal node and store both the key and the value.
If either of the children exist but have no value for this node then construct a
BlackBranch internal tree node pointing at the children.
If we have all four parameters then we have an internal node that also has a
value associated with it. Save the value and the children in a BlackBranchVal
node.
static Black black(Object key, Object val, Node left, Node right){
if(left == null && right == null)
{
if(val == null)
return new Black(key);
return new BlackVal(key, val);
}
if(val == null)
return new BlackBranch(key, left, right);
return new BlackBranchVal(key, val, left, right);
}
A Red node is a leaf node with a null value. This is constructed by Persistent-
TreeMaps [50] red method.
(Node [41])
PersistentTreeMap Red Class
Node blacken(){
return new Black(key);
}
Node redden(){
throw new UnsupportedOperationException("Invariant violation");
}
Node blacken(){
return new BlackVal(key, val);
}
-
48 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
Interior nodes of Clojures Red Black trees can have a value. A RedBranch
node is a leaf node with a null value and children, one of which could possibly
be null. This is constructed by PersistentTreeMaps [50] red method.
(Red [46])
PersistentTreeMap RedBranch Class
parent.left(), left.left()),
black(key, val(), left.right(), right));
else
return super.balanceRight(parent);
}
Node blacken(){
return new BlackBranch(key, left, right);
}
A RedBranchVal node is a leaf node with a value and children, one of which
could possibly be null. This is constructed by PersistentTreeMaps [50] red
method.
(RedBranch [48])
PersistentTreeMap RedBranchVal Class
Node blacken(){
return new BlackBranchVal(key, val, left, right);
}
}
If both of the children are null we must be constructing a leaf node. If the
value is null we need only construct a naked Red node, otherwise we construct
a RedVal node and store both the key and the value.
If either of the children exist but have no value for this node then construct a
RedBranch internal tree node pointing at the children.
If we have all four parameters then we have an internal node that also has a
value associated with it. Save the value and the children in a RedBranchVal
node.
static Red red(Object key, Object val, Node left, Node right){
if(left == null && right == null)
{
if(val == null)
return new Red(key);
return new RedVal(key, val);
}
if(val == null)
return new RedBranch(key, left, right);
return new RedBranchVal(key, val, left, right);
}
Node ( Black,
Node ( Red ,
Node ( Red, Node a, Node X, Node b ),
Node Y,
Node c
)
2.4. RED BLACK TREES 51
Node Z
Node d
)
Z => Y
Y d X Z
X c a b c d
a b
Z => Y
X d X Z
a Y a b c d
b c
52 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
X => Y
a Z X Z
Y d a b c d
b c
X => Y
a Y X Z
b A a b c d
c d
X Node ( Red,
Y
Z Node ( Black,
54 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
A => X
B b
Z Z
Case 2: The right child of a red node is red. We rewrite that as:
N => new
b
B Z Z
Case 3: Both children of a red node are black. Rewrite the node black.
N => A
B C B C
static Node rightBalance(Object key, Object val, Node left, Node ins){
if(ins instanceof Red && ins.right() instanceof Red)
return red(ins.key, ins.val(),
black(key, val, left, ins.left()),
ins.right().blacken());
else if(ins instanceof Red && ins.left() instanceof Red)
return red(ins.left().key, ins.left().val(),
black(key, val, left, ins.left().left()),
black(ins.key, ins.val(),
ins.left().right(), ins.right()));
else
return black(key, val, left, ins);
}
If we created a tree structure where the first level of lookup was the first 4
digits AAAA we would have a maximum of 9999 possible first level branches.
If the second layer of the tree were based on BBBB then it would also have a
branching factor of 9999. We eventually get a tree at most 4 layers deep that
handle all 101 6 possible values.
Alternatively we can reduce the fan-out by choosing 2 digits, so AA gives a
fan-out of 99 and this happens at each level. Now the tree is 8 layers deep in
order to contain all possible nodes.
Clojure has chosen a strategy based a bit pattern. So if we look at the bits in
groups of five we get this kind of a trie:
2.6. BIT-PARTITIONED HASH TRIES 57
G G F F F F F E E E E E D D D D D C C C C C B B B B B A A A A A
B B x B x x
x x C x x C C
x D x x x x x
x x
Notice that the trie has a fan-out of 25 or 32 at each level except the last. To
contain all possible 32 bit values requires a trie of at most depth 7. This means
that a lookup will take at most 7 possible fetches. For any reasonable set of
numbers this is a near constant time lookup.
One advantage of this scheme is the ability to mask out a subset of the word,
fully right-shift the bits, and use the resulting number as an index into the array.
Any masked off value has to be between 0 and 31 because the mask is 5 bits
wide.
The 5 bit mask function is part of [969] PersistentHashMap. Given a number of
bits to shift right (a multiple of 5 appears to be used everywhere) this routine
returns a number between 0 and 31.
This is used in the ArrayNode class. For instance, the find method
58 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
ISeq nodeSeq();
[955] ArrayNode
2.6. BIT-PARTITIONED HASH TRIES 59
[959] BitmapIndexedNode
[965] HashCollisionNode
2.6.1 Lists
2.6.2 Vectors
2.6.3 Hashmaps
Maps are associations of keywords and data. Hashmaps are untyped data struc-
tures, as opposed to Records which carry their type.
We can nest access using the thread first operator which threads arbitrary
pieces of code (as opposed to .. which threads Java classes).
The update-in function threads into a nested data structure and then applies
a function, in this case the inc function. This reduces the need to create inter-
mediate classes.
60 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
2.6.4 Seqs
(ASeq [571])
PersistentTreeMap Seq Class
2.7 Records
Records carry their type, in this case Person, as opposed to hashmaps which
are untyped data structures.
We can create a Var reference to a new record and set the address field to be a
new Address record.
62 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
And we can access them with the thread first operator which threads through
the set of accessors and then applies the function, in this case, the inc function.
This is similar to the .. operator except that it applies to any kind of operator,
not just Java operators.
People make claims that Lisp has no syntax . . . A more accurate thing to say
is that Lisp has the syntax of its data structures and that syntax has been
built upon to write code.
Stuart Halloway Clojure-Java Interp (Jan 19, 2011)
Function Calling
This is a list data structure with a first element of a Symbol and a second
element of a String. The first element is the function to be called and the
second element is the argument to that function.
Function Definition
(defn funname
"documentation string of the function"
[vector of arguments]
(function arg1 ... argN))
For example,
> (defn say says the name and age [name age] (str name age))
#user/say
The defn function creates the new function called say which takes 2 arguments
and returns a string with the two arguments separated by a space as in:
You can add meta information to your programs to improve the performance or
simplify the runtime lookup of classes. The syntax for metadata is a Symbol or
hashmap prefixed by a caret ^. For example, the say function returns a String
so we could write
If the item after the caret is a single Symbol it is expanded into a hashmap with
the keyword :tag and the Symbol. In our case, this is {:tag String}
new MyClass("arg1")
becomes
(MyClass. "arg1")
Math.PI
becomes
2.9. RECURSION 65
Math/PI
rnd.nextInt()
becomes
(.nextInt rnd)
person.getAddress().getZip()
becomes
The doto macro uses the first form to create an object upon which subsequent
method calls can be applied in series.
2.9 Recursion
2.14 Namespaces
Namespaces map to java packages.
66 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
2.18 Metadata
2.19 Concurrency
2.22 Symbols
Internally, a Clojure Symbol contains four pieces of data
Javas String class has an intern method [Ora11] which returns a canonical
representation for the string object. The String class maintains a pool of strings.
When Strings intern method is invoked the pool is searched for an equals match.
If the search succeeds then a reference to the stored string is returned, otherwise
the new string is added to the pool and a reference to this String object is
returned. Thus, the String name of a Symbol is a unique reference.
As pointed out in [Web11], Clojure creates a new Symbol for each occurence it
reads. The Symbols are not unique despite having equal names:
node2: clj
Clojure 1.3.0-alpha4
user=> (identical? a a)
false
user=> (= a a)
true
user=> (identical? (clojure.lang.Symbol/intern "a")
(clojure.lang.Symbol/intern "a"))
false
if(i == -1 || nsname.equals("/"))
return new Symbol(null, nsname.intern());
else
return
new Symbol(nsname.substring(0, i).intern(),
nsname.substring(i + 1).intern());
}
user=> (def a 1)
#user/a
Since the namespace was null the current namespace user was used. We can
see this from the toString result.
user=> (= user/a a)
false
user=> (def a 1)
#user/a
user=> user/a
1
2.23. THE LISP READER 69
try
{
for(; ;)
{
int ch = r.read();
while(isWhitespace(ch))
ch = r.read();
if(ch == -1)
70 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
{
if(eofIsError)
throw new Exception("EOF while reading");
return eofValue;
}
if(Character.isDigit(ch))
{
Object n = readNumber(r, (char) ch);
if(RT.suppressRead())
return null;
return n;
}
if(ch == + || ch == -)
{
int ch2 = r.read();
if(Character.isDigit(ch2))
{
unread(r, ch2);
Object n = readNumber(r, (char) ch);
if(RT.suppressRead())
return null;
return n;
}
unread(r, ch2);
}
LineNumberingPushbackReader rdr =
(LineNumberingPushbackReader) r;
//throw new Exception(String.format("ReaderError:(%d,1) %s",
// rdr.getLineNumber(), e.getMessage()), e);
throw new ReaderException(rdr.getLineNumber(), e);
}
}
The default values are specified in the syntax macro table. These are all class
objects that implement the [774] IFn interface which requires that they have an
invoke method.
For the special case of the # character there is a separate table, the [84] Dispatch
Macro Table which reads the next character and calls another dispatch function.
All of the special characters get their meaning from these tables.
By default the special syntax characters are
# a [83] DispatchReader
ch = \t;
break;
case r:
ch = \r;
break;
case n:
ch = \n;
break;
case \\:
break;
case ":
break;
case b:
ch = \b;
break;
case f:
ch = \f;
break;
case u:
{
ch = r.read();
if (Character.digit(ch, 16) == -1)
throw new Exception(
"Invalid unicode escape: \\u" + (char) ch);
ch =
readUnicodeChar((PushbackReader) r,ch,16,4,true);
break;
}
default:
{
if(Character.isDigit(ch))
{
ch =
readUnicodeChar(
(PushbackReader) r,ch,8,3,false);
if(ch > 0377)
throw new Exception(
"Octal escape sequence must be in range [0, 377].");
}
else
throw new Exception(
"Unsupported escape character: \\" + (char) ch);
}
}
}
sb.append((char) ch);
}
return sb.toString();
}
}
74 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
-
2.23. THE LISP READER 75
-
76 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
RT.list(SEQ,
RT.cons(CONCAT,
sqExpandList(
((IPersistentSet) form).seq()))));
}
else if(form instanceof ISeq ||
form instanceof IPersistentList)
{
ISeq seq = RT.seq(form);
if(seq == null)
ret = RT.cons(LIST,null);
else
ret =
RT.list(SEQ, RT.cons(CONCAT, sqExpandList(seq)));
}
else
throw new UnsupportedOperationException(
"Unknown Collection type");
}
else if(form instanceof Keyword
|| form instanceof Number
|| form instanceof Character
|| form instanceof String)
ret = form;
else
ret = RT.list(Compiler.QUOTE, form);
-
80 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
else if(token.equals("tab"))
return \t;
else if(token.equals("backspace"))
return \b;
else if(token.equals("formfeed"))
return \f;
else if(token.equals("return"))
return \r;
else if(token.startsWith("u"))
{
char c = (char) readUnicodeChar(token, 1, 4, 16);
if(c >= \uD800 && c <= \uDFFF) // surrogate code unit?
throw new Exception(
"Invalid character constant: \\u" +
Integer.toString(c, 16));
return c;
}
else if(token.startsWith("o"))
{
int len = token.length() - 1;
if(len > 3)
throw new Exception(
"Invalid octal escape sequence length: " + len);
int uc = readUnicodeChar(token, 1, len, 8);
if(uc > 0377)
throw new Exception(
"Octal escape sequence must be in range [0, 377].");
return (char) uc;
}
throw new Exception("Unsupported character: \\" + token);
}
unread(r, ch);
//% alone is first arg
if(ch == -1 || isWhitespace(ch) || isTerminatingMacro(ch))
{
return registerArg(1);
}
Object n = read(r, true, null, true);
if(n.equals(Compiler._AMP_))
return registerArg(-1);
if(!(n instanceof Number))
throw new IllegalStateException(
"arg literal must be %, %& or %integer");
return registerArg(((Number) n).intValue());
}
}
A DispatchReader reads the next character and uses it as an index into the [84]
dispatchMacros array. The reader function associated with the macro character,
in this case, is a [85] VarReader. This gets assigned to fn and then calls the
invoke method of the [85] VarReader class.
(AFn [509])
LispReader DispatchReader class
These are all defined in this table and they are explained in the following sec-
tions.
LispReader Dispatch Macro Table
is returned.
The single-quote () array entry contains a [85] VarReader function.
LispReader dispatchMacros VarReader statement
The VarReader invoke method reads the next object and returns the Var refer-
ence. (AFn [509])
LispReader VarReader class
(#mynamespace/functionname ...)
{
Object sym = argsyms.valAt(i);
if(sym == null)
sym = garg(i);
args = args.cons(sym);
}
}
Object restsym = argsyms.valAt(-1);
if(restsym != null)
{
args = args.cons(Compiler._AMP_);
args = args.cons(restsym);
}
}
return RT.list(Compiler.FN, args, form);
}
finally
{
Var.popThreadBindings();
}
}
}
2.25 Vars
2.26 Transients
Transients are intended to make your code faster. Transients ignore structural
sharing so time and space is saved. The underlying data structure is different.
For example, we thread a persistent map through some associations.
transients example
-
which results in the persistent map
transients example
{:c 3, :b 2, :a 1}
defn transient
(defn transient
"Alpha - subject to change.
Returns a new, transient version of the collection, in constant time."
{:added "1.1"
:static true}
[^clojure.lang.IEditableCollection coll]
(.asTransient coll))
The difference is that the transient call makes the persistent map is transformed
to a transient map. The assoc! call operates similar to assoc. The persistent!
call transforms the result to a persistent map.
There are several function that operate on transients, persistent!, conj!, as-
soc!, dissoc!, pop!, and disj!.
defn persistent!
(defn persistent!
"Alpha - subject to change.
Returns a new, persistent version of the transient collection, in
constant time. The transient collection cannot be used after this
call, any such use will throw an exception."
{:added "1.1"
:static true}
[^clojure.lang.ITransientCollection coll]
(.persistent coll))
2.26. TRANSIENTS 91
defn conj!
(defn conj!
"Alpha - subject to change.
Adds x to the transient collection, and return coll. The addition
may happen at different places depending on the concrete type."
{:added "1.1"
:static true}
[^clojure.lang.ITransientCollection coll x]
(.conj coll x))
defn assoc!
(defn assoc!
"Alpha - subject to change.
When applied to a transient map, adds mapping of key(s) to
val(s). When applied to a transient vector, sets the val at index.
Note - index must be <= (count vector). Returns coll."
{:added "1.1"
:static true}
([^clojure.lang.ITransientAssociative coll key val]
(.assoc coll key val))
([^clojure.lang.ITransientAssociative coll key val & kvs]
(let [ret (.assoc coll key val)]
(if kvs
(recur ret (first kvs) (second kvs) (nnext kvs))
ret))))
defn dissoc!
(defn dissoc!
"Alpha - subject to change.
Returns a transient map that doesnt contain a mapping for key(s)."
{:added "1.1"
:static true}
([^clojure.lang.ITransientMap map key] (.without map key))
([^clojure.lang.ITransientMap map key & ks]
(let [ret (.without map key)]
(if ks
92 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
defn pop!
(defn pop!
"Alpha - subject to change.
Removes the last item from a transient vector. If
the collection is empty, throws an exception. Returns coll"
{:added "1.1"
:static true}
[^clojure.lang.ITransientVector coll]
(.pop coll))
defn disj!
(defn disj!
"Alpha - subject to change.
disj[oin]. Returns a transient set of the same (hashed/sorted) type,
that does not contain key(s)."
{:added "1.1"
:static true}
([set] set)
([^clojure.lang.ITransientSet set key]
(. set (disjoin key)))
([set key & ks]
(let [ret (disj set key)]
(if ks
(recur ret (first ks) (next ks))
ret))))
-
2.26. TRANSIENTS 93
(0 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7}
-
which is only the first 8 values of the map.
However, if it is done in a recursive style, as in
transients example
(persistent!
(reduce (fn[m v] (assoc! m v v))
(transient {})
(range 1 21)))
-
yields all twenty pairs.
transients example
Transients can only be modified by the thread that creates them. Attemping to
access them will throw a java.util.concurrent.ExecutionException
transients example
It is also not possible to modify a Transient after the call to persistent! will
throw a java.lang.IllegalAccessError exception. So this will not work:
transients example
Transients do not work on every data structure. Lists, sets, and maps all throw
a java.lang.ClassCastException
transients example
(transient ())
(transient (sorted-set))
(transient (sorted-map))
94 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
2.27 Atoms
2.28 Refs
2.29 Agents
defn promise
(defn promise
"Alpha - subject to change.
Returns a promise object that can be read with deref/@, and set,
once only, with deliver. Calls to deref/@ prior to delivery will
block. All subsequent derefs will return the same delivered value
without blocking."
{:added "1.1"
:static true}
[]
(let [d (java.util.concurrent.CountDownLatch. 1)
v (atom nil)]
(reify
clojure.lang.IDeref
(deref [_] (.await d) @v)
clojure.lang.IPromiseImpl
(hasValue [this]
(= 0 (.getCount d)))
clojure.lang.IFn
(invoke [this x]
(locking d
(if (pos? (.getCount d))
(do (reset! v x)
2.30. PROMISE AND DELIVER 95
(.countDown d)
this)
(throw (IllegalStateException.
"Multiple deliver calls to a promise"))))))))
(deliver prom 1)
defn deliver
(defn deliver
"Alpha - subject to change.
Delivers the supplied value to the promise, releasing any pending
derefs. A subsequent call to deliver on a promise will throw an
exception."
{:added "1.1"
:static true}
[promise val] (promise val))
@prom
-
Note that dereferencing a promise before the value is delivered will block the
current thread. So you can have a future deliver on the promise from another
thread and wait on the result.
promise example
(do (future
(Thread/sleep 5000)
(deliver prom 1))
@prom)
2.31 Futures
Futures are a mechanism for executing a function in a different thread and
retrieving the results later.
Futures are useful for long running tasks that should not block, such as a UI
thread, communicating across networks, or a task that has a side-effect that
does not affect the main computation, such as displaying an image or printing.
For example,
future example
(do
(future
(Thread/sleep 3000)
(sendToPrinter "resume.pdf"))
(messages/plain-message "file being printed"))
A future can return a value. We define a var that will hold the value of a future
as in:
defn future-call
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; futures (needs proxy);;;;;;;;;;;;;;;;;;
(defn future-call
"Takes a function of no args and yields a future object that will
invoke the function in another thread, and will cache the result and
return it on all subsequent calls to deref/@. If the computation has
not yet finished, calls to deref/@ will block."
{:added "1.1"
:static true}
[f]
(let [f (binding-conveyor-fn f)
fut (.submit clojure.lang.Agent/soloExecutor ^Callable f)]
(reify
clojure.lang.IDeref
(deref [_] (.get fut))
java.util.concurrent.Future
(get [_] (.get fut))
(get [_ timeout unit] (.get fut timeout unit))
(isCancelled [_] (.isCancelled fut))
(isDone [_] (.isDone fut))
(cancel [_ interrupt?] (.cancel fut interrupt?)))))
defmacro future
2.31. FUTURES 97
(defmacro future
"Takes a body of expressions and yields a future object that will
invoke the body in another thread, and will cache the result and
return it on all subsequent calls to deref/@. If the computation has
not yet finished, calls to deref/@ will block."
{:added "1.1"}
[& body] (future-call (^{:once true} fn* [] ~@body)))
future var
(def result
(future
(Thread/sleep 3000)
"The future has arrived"))
dereference @ future We can get the value of this var using the dereference
operator @.
future dereference
@result
defn future?
(defn future?
"Returns true if x is a future"
{:added "1.1"
:static true}
[x] (instance? java.util.concurrent.Future x))
98 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
defn future-done?
(defn future-done?
"Returns true if future f is done"
{:added "1.1"
:static true}
[^java.util.concurrent.Future f] (.isDone f))
-
which, if it successfully cancels the future returns true.
defn future-cancel
(defn future-cancel
"Cancels the future, if possible."
{:added "1.1"
:static true}
[^java.util.concurrent.Future f] (.cancel f true))
We can test if the future was cancelled with the future-cancelled? function
future cancelled
-
2.32. MULTIMETHODS 99
defn future-cancelled?
(defn future-cancelled?
"Returns true if future f is cancelled"
{:added "1.1"
:static true}
[^java.util.concurrent.Future f] (.isCancelled f))
2.32 MultiMethods
2.33 Deftype
2.34 Defrecord
Define a record object with slots named :a, :b, and :c
> (:b f)
2
or
> (.b f)
2
> (class f)
user.Foo
100 CHAPTER 2. FROM IDEAS TO IMPLEMENTATION
We can apply normal functions to the query information about the class of f.
We see that the Var f references a record class which has a number of interesting
properties. In particular, given this record we know many things about its
properties.
[797] ILookup
Object Since records are Java Objects they can be compared with the
default equals method and can be stored in containers or passed as ar-
guments.
Map Since records implement Map they provide three collection views, as
a set of keys, as a set of values, and as a set of key-value mappings.
[1140] Seqable
[796] IKeywordLookup
Iterable Since records are Iterable they implement an iterator method
that allows them to be the target of a foreach statement.
[799] IMeta
[800] IPersistentCollection
[801] IPersistentMap
[768] Counted
[800] IObj
Serializable Since records are Serializable the implement writeObject and
readObject methods that make it possible to stream the state of the
object out and recreate it by reading it back.
[576] Associative
2.35. PROTOCOLS 101
2.35 Protocols
> (defprotocol coerce
coerce between things
(as-file [x] Coerce argument to a file)
(as-url [x] Coerce argument to a URL))
coerce
Defprotocol defines a named set of generic functions which dispatch on the type
of the first argument. The functions are defined in the same namespace (and
thus the same Java package) as the protocol.
Similar to Java interfaces but are functions, not associated with classes. In this
example, we mimic the Java idioms. A record is like class and coerce is like an
interface so we are saying that the class Name implements the as-file method
of interface coerce.
Extending inline
> (as-file n)
#File johndoe
> (myNewFunction 1)
2
2.36 Prototypes
2.37 Genclass
2.37.1 Overriding protected methods
In order to override a protected method and call the superclass you need to
make the protected method available under an alternate name. For examle, to
extend Java class Foo with fooMethod(bar) you would use
(ns.com.example.subclass-of-Foo
(:gen-class :extends com.example.Foo
:exposes-methods {fooMethod fooSuperMethod}))
2.38 Proxies
2.39 Macros
2.40 Iterators
(Iterator [1723])
PersistentTreeMap NodeIterator Class
(Iterator [1723])
PersistentTreeMap KeyIterator Class
NodeIterator it;
KeyIterator(NodeIterator it){
this.it = it;
}
(Iterator [1723])
PersistentTreeMap ValIterator Class
ValIterator(NodeIterator it){
this.it = it;
}
-
2.41. GENERATING JAVA CLASSES 105
107
108 CHAPTER 3. WRITING IDIOMATIC CLOJURE
Chapter 4
sim-world-setup
109
110 CHAPTER 4. THE ANTS DEMO (KAI WU)
(defstruct cell :food :pher) ; May also have :ant and :home values
Next, the world function creates the 2-dimensional board of cells (here, a
square of 80x80 cells), represented as vectors (rows or the vertical y-dimension)
of a vector (the horizontal x-dimension columns in one row):
sim-world-board-creation
4.1. THE SIMULATION WORLD 111
-
Reading the above:
Start with the innermost map call, which uses an anonymous function to
create one column of 80 cells, per (range dim). The struct returns a
new structmap instance using the earlier cell as the basis, initializing the
:food and :pher values to zero.
But notice that struct is wrapped with a transactional ref, and heres
the first glimpse of Clojures concurrency powers. With each cell being
stateful (possibly time-varying values of :food, :pher, :ant, and :home
values) and with multiple threads updating the board and board elements,
wed typically think of using locks on each cell when updating its state.
But in Clojure with its software transactional memory (STM), we just
use ref for safe references to mutable collections (here, a struct) - all
changes to a cell will then be atomic, consistent, and isolated! 1 Like
using an RDBMS, you dont need to manually manage concurrency.
Once you understand the innermost (ref (struct cell 0 0 )) map call,
the rest of (def world...) is straightforward: apply uses vector as a
constructor function with the map function producing the vectors argu-
ments, creating a column in the 2-D board.
Then the pattern is repeated in the outermost (apply vector (map...))
call, creating all the columns of the 2-D board.
Note that as defined, each vector in world (again, a 2-D vector of vectors)
corresponds to an x-position, and of course, within that vector are the y-
positions (here, a total of 80 cells).
The place function is a selector function (think of place as the noun, not the
verb) returning particular cells in the 2-D world. Once we have a cell, we can
then mutate it to represent ants, food, and pheromones (or their absence):
place
1 STM is like a memory-only SQL database, thus the last property of being
durable/persistent wont be satisfied.
112 CHAPTER 4. THE ANTS DEMO (KAI WU)
(defn place [x y]
(-> world (nth x) (nth y)))
place takes a single vector argument (having two elements x and y), then
applies the thrush operator (the arrow-like >) on the world object, first
selecting the column (nth x) on world, then the row (nth y) on that
column.
The thrush operator helps make code more concise, and arguably clearer: in-
stead of reading code inside-out to mentally evaluate it, we can read it left-
to-right. 2 Consider how the equivalent place function would look without
thrushing:
ants-defined
(defstruct ant :dir) ; Always has dir heading; may also have :food
(defn create-ant
"Create an ant at given location, returning an ant agent on the location."
[location direction]
(sync nil
(let [the-place (place location)
the-ant (struct ant direction)]
(alter the-place assoc :ant the-ant)
(agent location))))
-
2 Apparently Clojures thrush is not quite a true thrush, see Michael Fogus article at
blog.fogus.me/2010/09/28/thrush-in-clojure-redux
4.1. THE SIMULATION WORLD 113
home-setup
(defn setup
"Places initial food and ants, returns seq of ant agents."
[]
(sync nil
(dotimes [i food-places]
(let [p (place [(rand-int dim) (rand-int dim)])]
(alter p assoc :food (rand-int food-range))))
(doall
(for [x home-range y home-range]
(do
(alter (place [x y]) assoc :home true)
(create-ant [x y] (rand-int 8)))))))
The setup functions docstring tells us what its doing, so on to the details:
In sum, the setup function shows how to deal with state and its mutation in
Clojure: we started with a 2-D world-board of places (cells) as Clojure refs;
then we modify/mutate each place using alter. We can use various looping
functions such as dotimes and doall to process a batch of state-mutations (of
the world-board) atomically and consistently.
world-wrapping
(defn bound
"Returns given n, wrapped into range 0-b"
[b n]
(let [n (rem n b)]
(if (neg? n)
(+ n b)
n)))
;; Directions are 0-7, starting at north and going clockwise. These are
;; the 2-D deltas in order to move one step in a given direction.
(def direction-delta {0 [0 -1]
1 [1 -1]
2 [1 0]
3 [1 1]
4 [0 1]
5 [-1 1]
6 [-1 0]
7 [-1 -1]})
(defn delta-location
"Returns the location one step in the given direction. Note the
world is a torus."
[[x y] direction]
(let [[dx dy] (direction-delta (bound 8 direction))]
[(bound dim (+ x dx)) (bound dim (+ y dy))]))
-
116 CHAPTER 4. THE ANTS DEMO (KAI WU)
With the 2-D world board, we have the 8 cardinal directions (North, North-East,
East, etc.), and board edges that wrap-around to the opposite side - like the old
arcade games of the 1980s, e.g. Pac-Man and Asteroids. The functions bound
and delta-location help enforce these world-behaviors, while the definition of
direction-delta maps a movement in a cardinal direction to the corresponding
change in x and y. A few comments on each:
The bound function using the built-in rem (i.e. remainder) function is
straightforward. Observe how bound is used in delta-location to ensure
wrap-around behavior in: 1) cardinal directions; 2) the world-board, at
its edges given by dim.
direction-delta maps the eight cardinal directions (0 is North) to the
corresponding changes in [x y]. Note the syntax: its an array-map literal,
where the order of insertion of key-value pairs (here, keys 0-7) will be
preserved.
delta-location takes the current [x y] location and a direction, returning
the new corresponding location on the world-board.
Ant movements
Our ants need two behaviors to get around their world: turning (or changing
the direction they face), and stepping forward. Lets deal with turning first:
ant-agent-turn
(defn turn
"Turns the ant at the location by the given amount."
[loc amt]
(dosync
(let [p (place loc)
ant (:ant @p)]
(alter p assoc :ant (assoc ant :dir (bound 8 (+ (:dir ant) amt))))))
loc)
4.1. THE SIMULATION WORLD 117
The turn function takes two arguments, location and the amount of turn.
Whats interesting is the usage of the dosync function, which ensures the ants
turn - the changes of state within the assoc function calls - is all-or-nothing.
The ant gets a new direction per the innermost assoc, then the outermost assoc
updates the place with the updated ant.
Now for actual movement to a new place:
ant-agent-move
(defn move
"Moves the ant in the direction it is heading. Must be called in a
transaction that has verified the way is clear."
[startloc]
(let [oldp (place startloc)
ant (:ant @oldp)
newloc (delta-location startloc (:dir ant))
newp (place newloc)]
;; move the ant
(alter newp assoc :ant ant)
(alter oldp dissoc :ant)
;; leave pheromone trail
(when-not (:home @oldp)
(alter oldp assoc :pher (inc (:pher @oldp))))
newloc))
The move function changes state of both the ant and board, thus the doc-string
note that it must be called in a transaction. The code is self-explanatory, though
if pheromone is a new term to you, youll want to learn about a dominant
form of chemical communication on Earth. Whenever our artificial ant is not
within its home, it will secrete pheromone (inc the :pher value by 1) at the
place it just left, making it easier (more likely) for it and other ants to travel
between home and food locations in the future (instead of doing a completely
random walk).
When an ant finds food, it picks up one unit of it; when it returns home with
a food unit, it will drop its food there. These two interactions (each having
two steps) change the board, and as with the move function, they need to occur
atomically (all-or-nothing) to ensure the world is in a consistent state.
ant-agent-food
118 CHAPTER 4. THE ANTS DEMO (KAI WU)
Notice how similar the structure is for the two functions above; possibly theyre
candidates for macro refactoring.
Ant judgment
Our ants need some decision-making for their overall task of finding food and
bringing it home. As well see shortly, an ants behavior is based on two states,
either:
1. The ant does not have food, and is looking for it. In this mode, it weighs
the three map locations ahead of it (ahead, ahead-left, ahead-right) by the
presence of either food or pheromone.
2. The ant has food, and needs to bring it to the home box/location. Now
it weighs which of the three ahead-positions to take by the presence of
pheromone, or home.
So we need functions to express preference of the next location for an ant. The
functions rank-by and wrand help with that.
ant-agent-judgment-1
(defn rank-by
"Returns a map of xs to their 1-based rank when sorted by keyfn."
[keyfn xs]
4.1. THE SIMULATION WORLD 119
The rank-by function gives weights to where an ant will move next in the
simulation world. It takes two arguments, keyfn and xs - but what do those
args look like, and where is rank-by used? In the behave function below;
youll see that the keyfn checks for the presence of :food, :pher, or :home
- in the three cells (board locations) of the xs vector of [ahead ahead-left
ahead-right]. 3
Next: The wrand function helps with the larger task of randomizing which
location/cell the ant moves to next in a weighted manner; i.e. the dice are
loaded with rank-by, then rolled here:
ant-agent-judgment-2
3 Remember that :food, :pher, and :home are mutually exclusive in a cell. When an ant
wants to go home with food, and the home cell(s) is ahead of it, it will always go home, there
wont be competing :pher presence.
120 CHAPTER 4. THE ANTS DEMO (KAI WU)
(defn wrand
"Given a vector of slice sizes, returns the index of a slice given a
random spin of a roulette wheel with compartments proportional to
slices."
[slices]
(let [total (reduce + slices)
r (rand total)]
(loop [i 0 sum 0]
(if (< r (+ (slices i) sum))
i
(recur (inc i) (+ (slices i) sum))))))
How is wrand used? Like rank-by, look in the behave function: its single
argument of slices is a vector of 3 integers (from rank-by above), corresponding
to the relative desirability of the 3 cells ahead of the ant. So if the slices argument
looked like [0 3 1], that would correspond to zero probability of moving ahead,
and 3/4 chance moving to the ahead-left cell over the ahead-right cell.
The let value total uses reduce to set the upper bound on the random
number; loosely like setting the maximum number of faces on the die to
be rolled (albeit that some die numbers are geometrically impossible).
The rand function returns a random floating point number from 0 (inclu-
sive) to n (exclusive).
Heres the only looping construct in the entire ants program: its analogous
to checking which compartment of the roulette wheel the ball fell in. The
if checks if r fell into the current pocket - the size of which is given by
(slices i). If yes, return the index corresponding to that pocket; if not,
check the next pocket/slice.
The behave function below is the largest one, so it helps to keep in mind its
main parts while diving into details:
Also, consider the context of how behave is first used: within the main invo-
cation at the end, theres the expression:
So the behave function is called on every ant agent via the send-off function,
which is how Clojure dispatches potentially blocking actions to agents. And
there certainly are potentially blocking actions when using behave, since ants
may try to move into the same cell, try to acquire the same food, etc.
ant-agent-behave
(defn behave
"The main function for the ant agent."
[loc]
(let [p (place loc)
ant (:ant @p)
ahead (place (delta-location loc (:dir ant)))
ahead-left (place (delta-location loc (dec (:dir ant))))
ahead-right (place (delta-location loc (inc (:dir ant))))
places [ahead ahead-left ahead-right]]
;; Old way of Java interop: (. Thread (sleep ant-sleep-ms))
;; New idiomatic way is,
(Thread/sleep ant-sleep-ms)
(dosync
(when running
(send-off *agent* #behave))
(if (:food ant)
;; Then take food home:
(cond
(:home @p)
(-> loc drop-food (turn 4))
(and (:home @ahead) (not (:ant @ahead)))
(move loc)
:else
(let [ranks (merge-with +
(rank-by (comp #(if (:home %) 1 0) deref) places)
(rank-by (comp :pher deref) places))]
(([move #(turn % -1) #(turn % 1)]
(wrand [(if (:ant @ahead) 0 (ranks ahead))
(ranks ahead-left) (ranks ahead-right)]))
loc)))
;; No food, go foraging:
(cond
(and (pos? (:food @p)) (not (:home @p)))
(-> loc take-food (turn 4))
(and (pos? (:food @ahead)) (not (:home @ahead)) (not (:ant @ahead)))
(move loc)
122 CHAPTER 4. THE ANTS DEMO (KAI WU)
:else
(let [ranks (merge-with +
(rank-by (comp :food deref) places)
(rank-by (comp :pher deref) places))]
(([move #(turn % -1) #(turn % 1)]
(wrand [(if (:ant @ahead) 0 (ranks ahead))
(ranks ahead-left) (ranks ahead-right)]))
loc)))))))
or
(Thread/sleep ant-sleep-ms)
The first version uses the dot special form and in particular, the
(Classname/staticMethod args*)
Beyond syntax, the point of this expression is to slow down an ant (one
ant-agent per thread) between their movements, so you can see in the UI
what theyre doing, and theyll appear more realistic.
But more interesting still: in this highly concurrent program, the sleep expres-
sion is about the *only explicit reference to threads* in the entire code, i.e. one
of the very few leaky abstractions hinting at Clojures use of underlying JVM
4.1. THE SIMULATION WORLD 123
concurrency constructs. Besides this call, there are no locks, and no explicit
thread allocations.
The main dosync call
Next, lets look at whats going on within the dosync transaction.
Repeating asynchronously, without looping
The first expression is:
Initially this may seem strange; arent we in the behave function because send-
off already called it before entering it? Wont this just loop uselessly, not hitting
the core if code below? Not quite:
Also, note the # sharp-quote, before behave; this is a Clojure Var, one of
Clojures mutable reference types. Its just syntactic sugar for (var behave).
Invoking a Var referring to a function is the same as invoking the function
itself...so why bother with it? I dont know; heres what I could find:
send vs. send-off - send uses threadpool of fixed size which has low switch-
ing overhead but blocking can dry up the threadpool. By contrast, send-
off uses a dynamic threadpool and blocking is tolerated - and thats the
right approach here as ant contention for the same location/food can cer-
tainly cause (temporary) blocking.
124 CHAPTER 4. THE ANTS DEMO (KAI WU)
If the ant has food, take it home; the cond specifies 3 sub-cases:
1. At a home cell, drop the food and turn around 180 degrees, to exit
home for more food.
2. If a home cell is ahead, move to it.
3. Otherwise, do a ranking of cells ahead (places has the cells ahead,
ahead-left, ahead-right) per presence of pheromones, or home, and
then randomly select from those 3 cells per their ranking/weighting.
evaporate
(defn evaporate
"Causes all the pheromones to evaporate a bit."
[]
(dorun
(for [x (range dim) y (range dim)]
(dosync
(let [p (place [x y])]
(alter p assoc :pher (* evap-rate (:pher @p))))))))
For a bit of realism and a cleaner UI/visual, its useful to have the ants
pheromones diminish and evaporate from the world over time. The evapo-
rate function fulfills that requirement:
dosync is used as before, for lock-free updating of a place cell. Here, the
desired side-effect/mutation is to update the :pher value at the place
cell with a lower number.
Well see shortly that evaporate will run every second, a process that (like the
ants) will be handled asynchronously using a Clojure agent.
4.2 The UI
The user interface for the ants relies heavily on Clojures Java inter-operation
capabilities. But as well see, its more than just wrapping calls to Java.
clojureUI
;;;;;;;;;;;;;;;;;;;;;;;; UI ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(import
(java.awt Color Graphics Dimension)
(java.awt.image BufferedImage)
(javax.swing JPanel JFrame))
The import pulls in classes from Javas Abstract Window Toolkit (AWT) pack-
age, and from the Java Swing package. Assuming unfamiliarity with Java Swing,
lets describe the classes used:
The Color class encapsulates a color in the standard RGB color space.
In the code below, its usage as a constructor for a color instance follows
several arities:
4 integer arguments: r, g, b, and a for the alpha/transparency (0
transparent, 255 opaque)
3 integer arguments: r g b
1 argument: not a constructor call, but an access of a predefined
static Color field by name, returning the color in the RGB color
space.
The Graphics class is an abstract base class for all graphics contexts, i.e.
a Graphics instance holds the current state data needed for rendering
it: the Component object on which to draw, the current clip, color, and
126 CHAPTER 4. THE ANTS DEMO (KAI WU)
font, etc. Below, well see that the Clojure functions that take a Graphics
instance as an argument:
fill-cell
render-ant
render-place
render
By default, cells are empty; drawing cells having food or ant-deposited pheromones
is done by filling with symbolic colors - here by running the Java methods set-
Color and fillRect:
UI-fill-cell
-
Note the use of the doto function here and in many places below: in Java, pro-
cedural mutation of a newly constructed instance is common for initialization.
Clojures doto function is meant to be more concise in specifying the target
object just once, and then methods/setters acting on it and then returning it,
implicitly.
Drawing an ant: the graphical appearance of an ant is just a (5-pixel long) line
pointing in one of the 8 cardinal directions, of two different colors (having food
or not):
UI-render-ant
-
Note the cleverly concise destructuring for the start and end drawing coordi-
nates, needed in AWTs drawLine method.
If a cell in the ants world is not empty, it has one or more of three things
present: pheromone, food, or an ant. The render-place function updates the
cells appearance accordingly:
UI-render-place
(defn render-place [g p x y]
(when (pos? (:pher p))
(fill-cell g x y (new Color 0 255 0
(int (min 255 (* 255 (/ (:pher p) pher-scale)))))))
(when (pos? (:food p))
(fill-cell g x y (new Color 255 0 0
(int (min 255 (* 255 (/ (:food p) food-scale)))))))
128 CHAPTER 4. THE ANTS DEMO (KAI WU)
(when (:ant p)
(render-ant (:ant p) g x y)))
Finally, the render function ties everything together: initializing the UI/window
appearance by applying render place to every cell, and also drawing the home
space of the ants. Note the heavy usage of the dot special form: the UI code
relies heavily on Java, though Clojures for and doto help us avoid Java boil-
erplate and stay concise:
UI-render
(* scale dim)
(* scale dim)))))
Animation, panel-by-panel
Now for bringing the static starting picture to life - like the cartoons of old,
the animation function will draw the next state of the main panel displaying
the ants. Below, Hickey uses the queue-itself-then-run, again-and-again code
pattern weve seen before (above, in updating an ants state):
UI-animation
Finally, we need another agent to handle one more time-track of changes: evap-
oration, using the evaporate function defined above.
UI-evaporation
-
130 CHAPTER 4. THE ANTS DEMO (KAI WU)
(do
(load-file "./literate-ants.clj")
(def ants (setup))
(send-off animator animation)
(dorun (map #(send-off % behave) ants))
(send-off evaporator evaporation))
-
Either way youll see a new window appear with a white background, blue
square representing the ants home, red squares of food, black or red (w/ food)
moving lines representing each ant, and green squares for pheromones in various
concentrations. A lot happening concurrently, with no locks, and beautifully
concise code - welcome to Clojure!
\getchunk{sim-world-setup}
\getchunk{cell}
\getchunk{sim-world-board-creation}
\getchunk{place}
\getchunk{ants-defined}
\getchunk{home-setup}
\getchunk{world-wrapping}
\getchunk{ant-agent-judgment-2}
\getchunk{ant-agent-turn}
\getchunk{ant-agent-move}
\getchunk{ant-agent-food}
\getchunk{ant-agent-judgment-1}
\getchunk{ant-agent-behave}
\getchunk{evaporate}
\getchunk{clojureUI}
\getchunk{UI-scale}
\getchunk{UI-fill-cell}
\getchunk{UI-render-ant}
\getchunk{UI-render-place}
\getchunk{UI-render}
\getchunk{UI-panel}
\getchunk{UI-animation}
\getchunk{UI-evaporation}
4.3. RUNNING THE PROGRAM 131
\getchunk{runtheprogram}
-
132 CHAPTER 4. THE ANTS DEMO (KAI WU)
Chapter 5
Parallel Processing
133
134 CHAPTER 5. PARALLEL PROCESSING
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8
as
which then allows the compiler to add the subterms and then add their results.
Steele identifies several mathematical properties that the compiler can exploit:
5.2. STEELES PARALLEL IDEAS 135
associativity
commutativity
What are the parallel operators. Which of these parallel operators are thinking
vs implementation.
Could the add tree be log 32 since add can be multi-arg? Is there a mapping
from persistent trees to parallel code?
Clojure could partition tasks based on log32. A better, more fine grained alter-
native is to use [55] bit-partitioning to split tasks based on the size of the task
and the number of processors. That is,
so
||
|| ||
23 47 18 11
but the same list could be:
||
|| 11
|| 18
23 47
or
||
|| ||
23 || 18 ||
47 / / 11
or
||
23 ||
47 ||
18 ||
11 /
-
so, for example,
(map ( (x) (* x x)) (1 2 3)) (1 4 9)
Steele reduce
-
so, for example,
138 CHAPTER 5. PARALLEL PROCESSING
(reduce + 0 (1 4 9)) 14
Steele mapreduce
-
so, for example,
(mapreduce ( (x) (* x x)) + 0 (1 2 3)) Rightarrow 14
Steele length
5.5 MapReduce
142 CHAPTER 5. PARALLEL PROCESSING
Chapter 6
6.1 ant.build
build.xml
<project name="clojure" default="all" xmlns:mvn="urn:maven-artifact-ant">
<description>
Build with "ant jar" and then start the
REPL with: "java -cp clojure.jar clojure.main".
You will need to install the Maven Ant
Tasks to ${ant.home}/lib in order to execute
the nightly-build or stable-build targets.
</description>
<target name="debug">
<echo message="${deployment.url}"/>
</target>
143
144 CHAPTER 6. THE ANT BUILD SEQUENCE
<condition property="clojure.version.incremental.label"
value=".${clojure.version.incremental}"
else="">
<length string="${clojure.version.incremental}"
when="greater" length="0" />
</condition>
<condition property="clojure.version.qualifier.label"
value="-${clojure.version.qualifier}"
else="">
<length string="${clojure.version.qualifier}" when="greater"
length="0" />
</condition>
<condition property="clojure.version.interim.label"
value="-SNAPSHOT"
else="">
<!-- We place -SNAPSHOT whenever interim is not set to false, not
only if interim is set to true (this is less typo prone in the
worst case -->
<not><equals arg1="${clojure.version.interim}" arg2="false"
trim="true"/></not>
</condition>
<!-- general filterset for use when clojure version must be copied -->
<filterset id="clojure-version-filterset">
<filter token="clojure-version" value="${clojure.version.label}"/>
</filterset>
location="clojure-sources-${clojure.version.label}.jar"/>
<property name="snapshot.repo.dir"
location="/var/www/maven-snapshot-repository"/>
<property name="stable.repo.dir" location="/var/www/maven-repository"/>
<target name="init-version">
<copy file="pom-template.xml"
tofile="pom.xml">
<filterset refid="clojure-version-filterset"/>
</copy>
<!--prevents users from modifying accidentally the generated
pom.xml works only on linux.-->
<chmod file="pom.xml" perm="ugo-w"/>
</target>
<arg value="clojure.test.tap"/>
<arg value="clojure.test.junit"/>
<arg value="clojure.pprint"/>
<arg value="clojure.java.io"/>
<arg value="clojure.repl"/>
<arg value="clojure.java.browse"/>
<arg value="clojure.java.javadoc"/>
<arg value="clojure.java.shell"/>
<arg value="clojure.java.browse-ui"/>
<arg value="clojure.string"/>
<arg value="clojure.data"/>
<arg value="clojure.reflect"/>
</java>
</target>
<target name="build"
description="Build Clojure (compilation only, no tests)."
depends="compile-java, compile-clojure"/>
<target name="compile-tests"
description="Compile the subset of tests that require compilation.">
<delete dir="${test-classes}"/>
<mkdir dir="${test-classes}"/>
<java classname="clojure.lang.Compile"
classpath="${test-classes}:${test}:${build}:${cljsrc}"
failonerror="true">
<sysproperty key="clojure.compile.path" value="${test-classes}"/>
<arg value="clojure.test-clojure.protocols.examples"/>
<arg value="clojure.test-clojure.genclass.examples"/>
</java>
</target>
<target name="test"
description="Run clojure tests without recompiling clojure."
depends="compile-tests">
<java classname="clojure.main" failonerror="true">
<classpath>
<path location="${test-classes}"/>
<path location="${test}"/>
<path location="${build}"/>
<path location="${cljsrc}"/>
</classpath>
<arg value="-e"/>
<arg value=
"(require (clojure [test-clojure :as main])) (main/run-ant)"/>
</java>
</target>
<target name="all"
depends=
"build,test,clojure-jar,clojure-jar-slim,clojure-jar-sources"/>
<target name="clean"
description="Remove autogenerated files and directories.">
<delete dir="${build}"/>
<delete dir="${test-classes}"/>
<delete dir="${dist}"/>
148 CHAPTER 6. THE ANT BUILD SEQUENCE
<delete file="pom.xml"/>
<delete verbose="true">
<fileset dir="${basedir}" includes="*.jar"/>
<fileset dir="${basedir}" includes="*.zip"/>
</delete>
</target>
<target name="setup-maven">
<typedef resource="org/apache/maven/artifact/ant/antlib.xml"
uri="urn:maven-artifact-ant"/>
</target>
release, ]
clean:
[delete] Could not find file BASE/pom.xml to delete.
init:
[mkdir] Created dir: BASE/classes
Project base dir set to: BASE
[antcall] calling target(s) [init-version] in build file
BASE/build.xml
parsing buildfile BASE/build.xml with
URI = file:BASE/build.xml
Project base dir set to: BASE
Override ignored for property "src"
Override ignored for property "test"
Override ignored for property "jsrc"
Override ignored for property "cljsrc"
Override ignored for property "build"
Override ignored for property "test-classes"
Override ignored for property "dist"
Override ignored for property "deployment.url"
[property] Loading BASE/src/clj/clojure/version.properties
Override ignored for property "clojure.version.qualifier"
Override ignored for property "clojure.version.major"
Override ignored for property "clojure.version.interim"
Override ignored for property "clojure.version.incremental"
Override ignored for property "clojure.version.minor"
Override ignored for property "clojure.version.incremental.label"
Override ignored for property "clojure.version.qualifier.label"
Override ignored for property "clojure.version.interim.label"
Override ignored for property "clojure.version.label"
Override ignored for property "clojure_noversion_jar"
Override ignored for property "slim_noversion_jar"
Override ignored for property "src_noversion_jar"
Override ignored for property "clojure_jar"
Override ignored for property "slim_jar"
Override ignored for property "src_jar"
Override ignored for property "snapshot.repo.dir"
Override ignored for property "stable.repo.dir"
Build sequence for target(s) init-version is [init-version]
Complete build sequence is
[init-version, setup-maven, clean, init, compile-java,
compile-clojure, build, compile-tests, test, clojure-jar,
clojure-jar-slim, clojure-jar-sources, all, ci-build,
nightly-build, debug, dist, release, ]
[antcall] Entering BASE/build.xml...
Build sequence for target(s) init-version is [init-version]
Complete build sequence is
[init-version, setup-maven, clean, init, compile-java,
compile-clojure, build, compile-tests, test, clojure-jar,
6.2. THE EXECUTION 151
init-version:
[copy] Copying 1 file to BASE
[copy] Copying BASE/pom-template.xml to BASE/pom.xml
Replacing: @clojure-version@ -> 1.3.0-master-SNAPSHOT
[chmod] Current OS is Linux
[chmod] Executing chmod with arguments:
[chmod] ugo-w
[chmod] BASE/pom.xml
[chmod]
[chmod] The characters around the executable and arguments are
[chmod] not part of the command.
[chmod] Applied chmod to 1 file and 0 directories.
[antcall] Exiting BASE/build.xml.
compile-java:
[javac] clojure/asm/AnnotationVisitor.java added as
clojure/asm/AnnotationVisitor.class doesnt exist.
[javac] clojure/asm/AnnotationWriter.java added as
clojure/asm/AnnotationWriter.class doesnt exist.
[javac] clojure/asm/Attribute.java added as
clojure/asm/Attribute.class doesnt exist.
[javac] clojure/asm/ByteVector.java added as
clojure/asm/ByteVector.class doesnt exist.
[javac] clojure/asm/ClassAdapter.java added as
clojure/asm/ClassAdapter.class doesnt exist.
[javac] clojure/asm/ClassReader.java added as
clojure/asm/ClassReader.class doesnt exist.
[javac] clojure/asm/ClassVisitor.java added as
clojure/asm/ClassVisitor.class doesnt exist.
[javac] clojure/asm/ClassWriter.java added as
clojure/asm/ClassWriter.class doesnt exist.
[javac] clojure/asm/Edge.java added as
clojure/asm/Edge.class doesnt exist.
[javac] clojure/asm/FieldVisitor.java added as
clojure/asm/FieldVisitor.class doesnt exist.
[javac] clojure/asm/FieldWriter.java added as
clojure/asm/FieldWriter.class doesnt exist.
[javac] clojure/asm/Frame.java added as
clojure/asm/Frame.class doesnt exist.
[javac] clojure/asm/Handler.java added as
clojure/asm/Handler.class doesnt exist.
[javac] clojure/asm/Item.java added as
clojure/asm/Item.class doesnt exist.
[javac] clojure/asm/Label.java added as
clojure/asm/Label.class doesnt exist.
[javac] clojure/asm/MethodAdapter.java added as
clojure/asm/MethodAdapter.class doesnt exist.
152 CHAPTER 6. THE ANT BUILD SEQUENCE
/usr/share/ant/lib/ant-apache-log4j.jar:
/usr/share/ant/lib/ant-apache-resolver.jar:
/usr/share/ant/lib/ant-trax.jar:
/usr/share/ant/lib/ant-apache-bcel.jar:
/usr/share/ant/lib/ant-junit.jar:
/usr/share/ant/lib/ant-nodeps.jar:
/usr/lib/jvm/java-6-sun-1.6.0.13/lib/tools.jar:
/usr/lib/jvm/java-6-sun-1.6.0.13/jre/lib/rt.jar:
/usr/lib/jvm/java-6-sun-1.6.0.13/jre/lib/jce.jar:
/usr/lib/jvm/java-6-sun-1.6.0.13/jre/lib/jsse.jar
[javac] -sourcepath
[javac] BASE/src/jvm
[javac] -target
[javac] 1.5
[javac] -g
[javac]
[javac] The characters around the executable and arguments are
[javac] not part of the command.
[javac] Files to be compiled:
BASE/src/jvm/clojure/asm/AnnotationVisitor.java
BASE/src/jvm/clojure/asm/AnnotationWriter.java
BASE/src/jvm/clojure/asm/Attribute.java
BASE/src/jvm/clojure/asm/ByteVector.java
BASE/src/jvm/clojure/asm/ClassAdapter.java
BASE/src/jvm/clojure/asm/ClassReader.java
BASE/src/jvm/clojure/asm/ClassVisitor.java
BASE/src/jvm/clojure/asm/ClassWriter.java
BASE/src/jvm/clojure/asm/Edge.java
BASE/src/jvm/clojure/asm/FieldVisitor.java
BASE/src/jvm/clojure/asm/FieldWriter.java
BASE/src/jvm/clojure/asm/Frame.java
BASE/src/jvm/clojure/asm/Handler.java
BASE/src/jvm/clojure/asm/Item.java
BASE/src/jvm/clojure/asm/Label.java
BASE/src/jvm/clojure/asm/MethodAdapter.java
BASE/src/jvm/clojure/asm/MethodVisitor.java
BASE/src/jvm/clojure/asm/MethodWriter.java
BASE/src/jvm/clojure/asm/Opcodes.java
BASE/src/jvm/clojure/asm/Type.java
BASE/src/jvm/clojure/asm/commons/AdviceAdapter.java
BASE/src/jvm/clojure/asm/commons/AnalyzerAdapter.java
BASE/src/jvm/clojure/asm/commons/CodeSizeEvaluator.java
BASE/src/jvm/clojure/asm/commons/EmptyVisitor.java
BASE/src/jvm/clojure/asm/commons/GeneratorAdapter.java
BASE/src/jvm/clojure/asm/commons/LocalVariablesSorter.java
BASE/src/jvm/clojure/asm/commons/Method.java
BASE/src/jvm/clojure/asm/commons/SerialVersionUIDAdder.java
BASE/src/jvm/clojure/asm/commons/StaticInitMerger.java
BASE/src/jvm/clojure/asm/commons/TableSwitchGenerator.java
BASE/src/jvm/clojure/lang/AFn.java
6.2. THE EXECUTION 159
BASE/src/jvm/clojure/lang/AFunction.java
BASE/src/jvm/clojure/lang/AMapEntry.java
BASE/src/jvm/clojure/lang/APersistentMap.java
BASE/src/jvm/clojure/lang/APersistentSet.java
BASE/src/jvm/clojure/lang/APersistentVector.java
BASE/src/jvm/clojure/lang/ARef.java
BASE/src/jvm/clojure/lang/AReference.java
BASE/src/jvm/clojure/lang/ASeq.java
BASE/src/jvm/clojure/lang/ATransientMap.java
BASE/src/jvm/clojure/lang/ATransientSet.java
BASE/src/jvm/clojure/lang/Agent.java
BASE/src/jvm/clojure/lang/ArityException.java
BASE/src/jvm/clojure/lang/ArrayChunk.java
BASE/src/jvm/clojure/lang/ArraySeq.java
BASE/src/jvm/clojure/lang/Associative.java
BASE/src/jvm/clojure/lang/Atom.java
BASE/src/jvm/clojure/lang/BigInt.java
BASE/src/jvm/clojure/lang/Binding.java
BASE/src/jvm/clojure/lang/Box.java
BASE/src/jvm/clojure/lang/ChunkBuffer.java
BASE/src/jvm/clojure/lang/ChunkedCons.java
BASE/src/jvm/clojure/lang/Compile.java
BASE/src/jvm/clojure/lang/Compiler.java
BASE/src/jvm/clojure/lang/Cons.java
BASE/src/jvm/clojure/lang/Counted.java
BASE/src/jvm/clojure/lang/Delay.java
BASE/src/jvm/clojure/lang/DynamicClassLoader.java
BASE/src/jvm/clojure/lang/EnumerationSeq.java
BASE/src/jvm/clojure/lang/Fn.java
BASE/src/jvm/clojure/lang/IChunk.java
BASE/src/jvm/clojure/lang/IChunkedSeq.java
BASE/src/jvm/clojure/lang/IDeref.java
BASE/src/jvm/clojure/lang/IEditableCollection.java
BASE/src/jvm/clojure/lang/IFn.java
BASE/src/jvm/clojure/lang/IKeywordLookup.java
BASE/src/jvm/clojure/lang/ILookup.java
BASE/src/jvm/clojure/lang/ILookupSite.java
BASE/src/jvm/clojure/lang/ILookupThunk.java
BASE/src/jvm/clojure/lang/IMapEntry.java
BASE/src/jvm/clojure/lang/IMeta.java
BASE/src/jvm/clojure/lang/IObj.java
BASE/src/jvm/clojure/lang/IPersistentCollection.java
BASE/src/jvm/clojure/lang/IPersistentList.java
BASE/src/jvm/clojure/lang/IPersistentMap.java
BASE/src/jvm/clojure/lang/IPersistentSet.java
BASE/src/jvm/clojure/lang/IPersistentStack.java
BASE/src/jvm/clojure/lang/IPersistentVector.java
BASE/src/jvm/clojure/lang/IPromiseImpl.java
BASE/src/jvm/clojure/lang/IProxy.java
BASE/src/jvm/clojure/lang/IReduce.java
160 CHAPTER 6. THE ANT BUILD SEQUENCE
BASE/src/jvm/clojure/lang/IRef.java
BASE/src/jvm/clojure/lang/IReference.java
BASE/src/jvm/clojure/lang/ISeq.java
BASE/src/jvm/clojure/lang/ITransientAssociative.java
BASE/src/jvm/clojure/lang/ITransientCollection.java
BASE/src/jvm/clojure/lang/ITransientMap.java
BASE/src/jvm/clojure/lang/ITransientSet.java
BASE/src/jvm/clojure/lang/ITransientVector.java
BASE/src/jvm/clojure/lang/Indexed.java
BASE/src/jvm/clojure/lang/IndexedSeq.java
BASE/src/jvm/clojure/lang/IteratorSeq.java
BASE/src/jvm/clojure/lang/Keyword.java
BASE/src/jvm/clojure/lang/KeywordLookupSite.java
BASE/src/jvm/clojure/lang/LazilyPersistentVector.java
BASE/src/jvm/clojure/lang/LazySeq.java
BASE/src/jvm/clojure/lang/LineNumberingPushbackReader.java
BASE/src/jvm/clojure/lang/LispReader.java
BASE/src/jvm/clojure/lang/LockingTransaction.java
BASE/src/jvm/clojure/lang/MapEntry.java
BASE/src/jvm/clojure/lang/MapEquivalence.java
BASE/src/jvm/clojure/lang/MethodImplCache.java
BASE/src/jvm/clojure/lang/MultiFn.java
BASE/src/jvm/clojure/lang/Named.java
BASE/src/jvm/clojure/lang/Namespace.java
BASE/src/jvm/clojure/lang/Numbers.java
BASE/src/jvm/clojure/lang/Obj.java
BASE/src/jvm/clojure/lang/PersistentArrayMap.java
BASE/src/jvm/clojure/lang/PersistentHashMap.java
BASE/src/jvm/clojure/lang/PersistentHashSet.java
BASE/src/jvm/clojure/lang/PersistentList.java
BASE/src/jvm/clojure/lang/PersistentQueue.java
BASE/src/jvm/clojure/lang/PersistentStructMap.java
BASE/src/jvm/clojure/lang/PersistentTreeMap.java
BASE/src/jvm/clojure/lang/PersistentTreeSet.java
BASE/src/jvm/clojure/lang/PersistentVector.java
BASE/src/jvm/clojure/lang/ProxyHandler.java
BASE/src/jvm/clojure/lang/RT.java
BASE/src/jvm/clojure/lang/Range.java
BASE/src/jvm/clojure/lang/Ratio.java
BASE/src/jvm/clojure/lang/Ref.java
BASE/src/jvm/clojure/lang/Reflector.java
BASE/src/jvm/clojure/lang/Repl.java
BASE/src/jvm/clojure/lang/RestFn.java
BASE/src/jvm/clojure/lang/Reversible.java
BASE/src/jvm/clojure/lang/Script.java
BASE/src/jvm/clojure/lang/SeqEnumeration.java
BASE/src/jvm/clojure/lang/SeqIterator.java
BASE/src/jvm/clojure/lang/Seqable.java
BASE/src/jvm/clojure/lang/Sequential.java
BASE/src/jvm/clojure/lang/Settable.java
6.2. THE EXECUTION 161
BASE/src/jvm/clojure/lang/Sorted.java
BASE/src/jvm/clojure/lang/StringSeq.java
BASE/src/jvm/clojure/lang/Symbol.java
BASE/src/jvm/clojure/lang/TransactionalHashMap.java
BASE/src/jvm/clojure/lang/Util.java
BASE/src/jvm/clojure/lang/Var.java
BASE/src/jvm/clojure/lang/XMLHandler.java
BASE/src/jvm/clojure/main.java
[javac] Note: Some input files use unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
compile-clojure:
[java] Executing /usr/lib/jvm/java-6-sun-1.6.0.13/jre/bin/java
with arguments:
[java] -Dclojure.compile.path=BASE/classes
[java] -classpath
[java] BASE/classes:BASE/src/clj
[java] clojure.lang.Compile
[java] clojure.core
[java] clojure.core.protocols
[java] clojure.main
[java] clojure.set
[java] clojure.xml
[java] clojure.zip
[java] clojure.inspector
[java] clojure.walk
[java] clojure.stacktrace
[java] clojure.template
[java] clojure.test
[java] clojure.test.tap
[java] clojure.test.junit
[java] clojure.pprint
[java] clojure.java.io
[java] clojure.repl
[java] clojure.java.browse
[java] clojure.java.javadoc
[java] clojure.java.shell
[java] clojure.java.browse-ui
[java] clojure.string
[java] clojure.data
[java] clojure.reflect
[java]
[java] The characters around the executable and arguments are
[java] not part of the command.
[java] Compiling clojure.core to BASE/classes
[java] Compiling clojure.core.protocols to BASE/classes
[java] Compiling clojure.main to BASE/classes
[java] Compiling clojure.set to BASE/classes
[java] Compiling clojure.xml to BASE/classes
[java] Compiling clojure.zip to BASE/classes
162 CHAPTER 6. THE ANT BUILD SEQUENCE
build:
BUILD SUCCESSFUL
Total time: 33 seconds
Chapter 7
jvm/clojure/asm/
7.1 AnnotationVisitor.java
AnnotationVisitor.java
\getchunk{France Telecom Copyright}
package clojure.asm;
/**
* A visitor to visit a Java annotation. The methods of this interface
* must be called in the following order: (<tt>visit<tt> |
* <tt>visitEnum<tt> | <tt>visitAnnotation<tt> | <tt>visitArray<tt>)*
* <tt>visitEnd<tt>.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public interface AnnotationVisitor{
/**
* Visits a primitive value of the annotation.
*
* @param name the value name.
* @param value the actual value, whose type must be {@link Byte},
* {@link Boolean}, {@link Character}, {@link Short},
* {@link Integer}, {@link Long}, {@link Float},
* {@link Double}, {@link String} or {@link Type}. This
* value can also be an array of byte, boolean, short,
* char, int, long, float or double values (this is
* equivalent to using {@link #visitArray visitArray}
* and visiting each array element in turn, but is more
* convenient).
163
164 CHAPTER 7. JVM/CLOJURE/ASM/
*/
void visit(String name, Object value);
/**
* Visits an enumeration value of the annotation.
*
* @param name the value name.
* @param desc the class descriptor of the enumeration class.
* @param value the actual enumeration value.
*/
void visitEnum(String name, String desc, String value);
/**
* Visits a nested annotation value of the annotation.
*
* @param name the value name.
* @param desc the class descriptor of the nested annotation class.
* @return a visitor to visit the actual nested annotation value, or
* <tt>null</tt> if this visitor is not interested in visiting
* this nested annotation. <i>The nested annotation value must be
* fully visited before calling other methods on this annotation
* visitor</i>.
*/
AnnotationVisitor visitAnnotation(String name, String desc);
/**
* Visits an array value of the annotation. Note that arrays of primitive
* types (such as byte, boolean, short, char, int, long, float or double)
* can be passed as value to {@link #visit visit}. This is what
* {@link ClassReader} does.
*
* @param name the value name.
* @return a visitor to visit the actual array value elements, or
* <tt>null</tt> if this visitor is not interested in visiting
* these values. The name parameters passed to the methods of
* this visitor are ignored. <i>All the array values must be
* visited before calling other methods on this annotation
* visitor</i>.
*/
AnnotationVisitor visitArray(String name);
/**
* Visits the end of the annotation.
*/
void visitEnd();
}
-
7.2. ANNOTATIONWRITER.JAVA 165
7.2 AnnotationWriter.java
(AnnotationVisitor [163])
AnnotationWriter.java
/**
* An {@link AnnotationVisitor} that generates annotations in
* bytecode form.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
final class AnnotationWriter implements AnnotationVisitor{
/**
* The class writer to which this annotation must be added.
*/
private final ClassWriter cw;
/**
* The number of values in this annotation.
*/
private int size;
/**
* <tt>true<tt> if values are named, <tt>false</tt> otherwise. Annotation
* writers used for annotation default and annotation arrays use unnamed
* values.
*/
private final boolean named;
/**
* The annotation values in bytecode form. This byte vector only contains
* the values themselves, i.e. the number of values must be stored as a
* unsigned short just before these bytes.
*/
private final ByteVector bv;
/**
* The byte vector to be used to store the number of values of this
* annotation. See {@link #bv}.
*/
private final ByteVector parent;
/**
* Where the number of values of this annotation must be stored in
166 CHAPTER 7. JVM/CLOJURE/ASM/
* {@link #parent}.
*/
private final int offset;
/**
* Next annotation writer. This field is used to store annotation lists.
*/
AnnotationWriter next;
/**
* Previous annotation writer. This field is used to store annotation
* lists.
*/
AnnotationWriter prev;
// -------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------
/**
* Constructs a new {@link AnnotationWriter}.
*
* @param cw the class writer to which this annotation must be added.
* @param named <tt>true<tt> if values are named,
* <tt>false</tt> otherwise.
* @param bv where the annotation values must be stored.
* @param parent where the number of annotation values must be stored.
* @param offset where in <tt>parent</tt> the number of annotation values
* must be stored.
*/
AnnotationWriter(
final ClassWriter cw,
final boolean named,
final ByteVector bv,
final ByteVector parent,
final int offset){
this.cw = cw;
this.named = named;
this.bv = bv;
this.parent = parent;
this.offset = offset;
}
// -------------------------------------------------------------------
// Implementation of the AnnotationVisitor interface
// -------------------------------------------------------------------
{
bv.putShort(cw.newUTF8(name));
}
if(value instanceof String)
{
bv.put12(s, cw.newUTF8((String) value));
}
else if(value instanceof Byte)
{
bv.put12(B, cw.newInteger(((Byte) value).byteValue()).index);
}
else if(value instanceof Boolean)
{
int v = ((Boolean) value).booleanValue() ? 1 : 0;
bv.put12(Z, cw.newInteger(v).index);
}
else if(value instanceof Character)
{
bv.put12(C,
cw.newInteger(((Character) value).charValue()).index);
}
else if(value instanceof Short)
{
bv.put12(S, cw.newInteger(((Short) value).shortValue()).index);
}
else if(value instanceof Type)
{
bv.put12(c, cw.newUTF8(((Type) value).getDescriptor()));
}
else if(value instanceof byte[])
{
byte[] v = (byte[]) value;
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(B, cw.newInteger(v[i]).index);
}
}
else if(value instanceof boolean[])
{
boolean[] v = (boolean[]) value;
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(Z, cw.newInteger(v[i] ? 1 : 0).index);
}
}
else if(value instanceof short[])
{
short[] v = (short[]) value;
168 CHAPTER 7. JVM/CLOJURE/ASM/
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(S, cw.newInteger(v[i]).index);
}
}
else if(value instanceof char[])
{
char[] v = (char[]) value;
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(C, cw.newInteger(v[i]).index);
}
}
else if(value instanceof int[])
{
int[] v = (int[]) value;
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(I, cw.newInteger(v[i]).index);
}
}
else if(value instanceof long[])
{
long[] v = (long[]) value;
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(J, cw.newLong(v[i]).index);
}
}
else if(value instanceof float[])
{
float[] v = (float[]) value;
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(F, cw.newFloat(v[i]).index);
}
}
else if(value instanceof double[])
{
double[] v = (double[]) value;
bv.put12([, v.length);
for(int i = 0; i < v.length; i++)
{
bv.put12(D, cw.newDouble(v[i]).index);
}
7.2. ANNOTATIONWRITER.JAVA 169
}
else
{
Item i = cw.newConstItem(value);
bv.put12(".s.IFJDCS".charAt(i.type), i.index);
}
}
}
}
// -------------------------------------------------------------------
// Utility methods
// -------------------------------------------------------------------
/**
* Returns the size of this annotation writer list.
*
* @return the size of this annotation writer list.
*/
int getSize(){
int size = 0;
AnnotationWriter aw = this;
while(aw != null)
{
size += aw.bv.length;
aw = aw.next;
}
return size;
}
/**
* Puts the annotations of this annotation writer list into the given
* byte vector.
*
* @param out where the annotations must be put.
*/
void put(final ByteVector out){
int n = 0;
int size = 2;
AnnotationWriter aw = this;
AnnotationWriter last = null;
while(aw != null)
{
++n;
size += aw.bv.length;
aw.visitEnd(); // in case user forgot to call visitEnd
aw.prev = last;
last = aw;
aw = aw.next;
}
out.putInt(size);
out.putShort(n);
aw = last;
while(aw != null)
{
out.putByteArray(aw.bv.data, 0, aw.bv.length);
aw = aw.prev;
7.3. ATTRIBUTE.JAVA 171
}
}
/**
* Puts the given annotation lists into the given byte vector.
*
* @param panns an array of annotation writer lists.
* @param out where the annotations must be put.
*/
static void put(final AnnotationWriter[] panns, final ByteVector out){
int size = 1 + 2 * panns.length;
for(int i = 0; i < panns.length; ++i)
{
size += panns[i] == null ? 0 : panns[i].getSize();
}
out.putInt(size).putByte(panns.length);
for(int i = 0; i < panns.length; ++i)
{
AnnotationWriter aw = panns[i];
AnnotationWriter last = null;
int n = 0;
while(aw != null)
{
++n;
aw.visitEnd(); // in case user forgot to call visitEnd
aw.prev = last;
last = aw;
aw = aw.next;
}
out.putShort(n);
aw = last;
while(aw != null)
{
out.putByteArray(aw.bv.data, 0, aw.bv.length);
aw = aw.prev;
}
}
}
}
7.3 Attribute.java
Attribute.java
package clojure.asm;
/**
* A non standard class, field, method or code attribute.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class Attribute{
/**
* The type of this attribute.
*/
public final String type;
/**
* The raw value of this attribute, used only for unknown attributes.
*/
byte[] value;
/**
* The next attribute in this attribute list. May be <tt>null</tt>.
*/
Attribute next;
/**
* Constructs a new empty attribute.
*
* @param type the type of the attribute.
*/
protected Attribute(final String type){
this.type = type;
}
/**
* Returns <tt>true</tt> if this type of attribute is unknown.
* The default implementation of this method always returns
* <tt>true</tt>.
*
* @return <tt>true</tt> if this type of attribute is unknown.
*/
public boolean isUnknown(){
return true;
}
/**
* Returns <tt>true</tt> if this type of attribute is a code attribute.
*
* @return <tt>true</tt> if this type of attribute is a code attribute.
*/
7.3. ATTRIBUTE.JAVA 173
/**
* Returns the labels corresponding to this attribute.
*
* @return the labels corresponding to this attribute, or
* <tt>null</tt> if this attribute is not a code attribute
* that contains labels.
*/
protected Label[] getLabels(){
return null;
}
/**
* Reads a {@link #type type} attribute. This method must return
* <i>new</i> {@link Attribute} object, of type {@link #type type},
* corresponding to the <tt>len</tt> bytes starting at the given offset,
* in the given class reader.
*
* @param cr the class that contains the attribute to be read.
* @param off index of the first byte of the attributes content
* in {@link ClassReader#b cr.b}. The 6 attribute header
* bytes, containing the type and the length of the
* attribute, are not taken into account
* here.
* @param len the length of the attributes content.
* @param buf buffer to be used to call
* {@link ClassReader#readUTF8 readUTF8},
* {@link ClassReader#readClass(int,char[]) readClass} or
* {@link ClassReader#readConst readConst}.
* @param codeOff index of the first byte of codes attribute content in
* {@link ClassReader#b cr.b}, or -1 if the attribute to
* be read is not a code attribute. The 6 attribute header
* bytes, containing the type and the length of the
* attribute, are not taken into account here.
* @param labels the labels of the methods code, or <tt>null</tt>
* if the attribute to be read is not a code attribute.
* @return a <i>new</i> {@link Attribute} object corresponding to the
* given bytes.
*/
protected Attribute read(
final ClassReader cr,
final int off,
final int len,
final char[] buf,
final int codeOff,
final Label[] labels){
Attribute attr = new Attribute(type);
174 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Returns the byte array form of this attribute.
*
* @param cw the class to which this attribute must be added.
* This parameter can be used to add to the constant
* pool of this class the items that corresponds to
* this attribute.
* @param code the bytecode of the method corresponding to this code
* attribute, or <tt>null</tt> if this attribute is not
* a code attributes.
* @param len the length of the bytecode of the method
* corresponding to this code attribute, or
* <tt>null</tt> if this attribute is not a code
* attribute.
* @param maxStack the maximum stack size of the method corresponding to
* this code attribute, or -1 if this attribute is not
* a code attribute.
* @param maxLocals the maximum number of local variables of the method
* corresponding to this code attribute, or -1 if this
* attribute is not a code attribute.
* @return the byte array form of this attribute.
*/
protected ByteVector write(
final ClassWriter cw,
final byte[] code,
final int len,
final int maxStack,
final int maxLocals){
ByteVector v = new ByteVector();
v.data = value;
v.length = value.length;
return v;
}
/**
* Returns the length of the attribute list that begins with this
* attribute.
*
* @return the length of the attribute list that begins with this
* attribute.
*/
final int getCount(){
int count = 0;
Attribute attr = this;
while(attr != null)
7.3. ATTRIBUTE.JAVA 175
{
count += 1;
attr = attr.next;
}
return count;
}
/**
* Returns the size of all the attributes in this attribute list.
*
* @param cw the class writer to be used to convert the attributes
* into byte arrays, with the {@link #write write}
* method.
* @param code the bytecode of the method corresponding to these
* code attributes, or <tt>null</tt> if these
* attributes are not code attributes.
* @param len the length of the bytecode of the method
* corresponding to these code attributes, or
* <tt>null</tt> if these attributes are not code
* attributes.
* @param maxStack the maximum stack size of the method corresponding to
* these code attributes, or -1 if these attributes are
* not code attributes.
* @param maxLocals the maximum number of local variables of the method
* corresponding to these code attributes, or -1 if
* these attributes are not code attributes.
* @return the size of all the attributes in this attribute list.
* This size includes the size of the attribute headers.
*/
final int getSize(
final ClassWriter cw,
final byte[] code,
final int len,
final int maxStack,
final int maxLocals){
Attribute attr = this;
int size = 0;
while(attr != null)
{
cw.newUTF8(attr.type);
size +=
attr.write(cw,code,len,maxStack,maxLocals).length + 6;
attr = attr.next;
}
return size;
}
/**
* Writes all the attributes of this attribute list in the given byte
* vector.
176 CHAPTER 7. JVM/CLOJURE/ASM/
*
* @param cw the class writer to be used to convert the attributes
* into byte arrays, with the {@link #write write}
* method.
* @param code the bytecode of the method corresponding to these
* code attributes, or <tt>null</tt> if these attributes
* are not code attributes.
* @param len the length of the bytecode of the method
* corresponding to these code attributes, or
* <tt>null</tt> if these attributes are not code
* attributes.
* @param maxStack the maximum stack size of the method corresponding to
* these code attributes, or -1 if these attributes are
* not code attributes.
* @param maxLocals the maximum number of local variables of the method
* corresponding to these code attributes, or -1 if
* these attributes are not code attributes.
* @param out where the attributes must be written.
*/
final void put(
final ClassWriter cw,
final byte[] code,
final int len,
final int maxStack,
final int maxLocals,
final ByteVector out){
Attribute attr = this;
while(attr != null)
{
ByteVector b = attr.write(cw, code, len, maxStack, maxLocals);
out.putShort(cw.newUTF8(attr.type)).putInt(b.length);
out.putByteArray(b.data, 0, b.length);
attr = attr.next;
}
}
}
7.4 ByteVector.java
ByteVector.java
/**
7.4. BYTEVECTOR.JAVA 177
/**
* The content of this vector.
*/
byte[] data;
/**
* Actual number of bytes in this vector.
*/
int length;
/**
* Constructs a new {@link ByteVector ByteVector} with a default initial
* size.
*/
public ByteVector(){
data = new byte[64];
}
/**
* Constructs a new {@link ByteVector ByteVector} with the given initial
* size.
*
* @param initialSize the initial size of the byte vector to be
* constructed.
*/
public ByteVector(final int initialSize){
data = new byte[initialSize];
}
/**
* Puts a byte into this byte vector. The byte vector is automatically
* enlarged if necessary.
*
* @param b a byte.
* @return this byte vector.
*/
public ByteVector putByte(final int b){
int length = this.length;
if(length + 1 > data.length)
{
enlarge(1);
}
178 CHAPTER 7. JVM/CLOJURE/ASM/
data[length++] = (byte) b;
this.length = length;
return this;
}
/**
* Puts two bytes into this byte vector. The byte vector is automatically
* enlarged if necessary.
*
* @param b1 a byte.
* @param b2 another byte.
* @return this byte vector.
*/
ByteVector put11(final int b1, final int b2){
int length = this.length;
if(length + 2 > data.length)
{
enlarge(2);
}
byte[] data = this.data;
data[length++] = (byte) b1;
data[length++] = (byte) b2;
this.length = length;
return this;
}
/**
* Puts a short into this byte vector. The byte vector is automatically
* enlarged if necessary.
*
* @param s a short.
* @return this byte vector.
*/
public ByteVector putShort(final int s){
int length = this.length;
if(length + 2 > data.length)
{
enlarge(2);
}
byte[] data = this.data;
data[length++] = (byte) (s >>> 8);
data[length++] = (byte) s;
this.length = length;
return this;
}
/**
* Puts a byte and a short into this byte vector. The byte vector is
* automatically enlarged if necessary.
*
7.4. BYTEVECTOR.JAVA 179
* @param b a byte.
* @param s a short.
* @return this byte vector.
*/
ByteVector put12(final int b, final int s){
int length = this.length;
if(length + 3 > data.length)
{
enlarge(3);
}
byte[] data = this.data;
data[length++] = (byte) b;
data[length++] = (byte) (s >>> 8);
data[length++] = (byte) s;
this.length = length;
return this;
}
/**
* Puts an int into this byte vector. The byte vector is automatically
* enlarged if necessary.
*
* @param i an int.
* @return this byte vector.
*/
public ByteVector putInt(final int i){
int length = this.length;
if(length + 4 > data.length)
{
enlarge(4);
}
byte[] data = this.data;
data[length++] = (byte) (i >>> 24);
data[length++] = (byte) (i >>> 16);
data[length++] = (byte) (i >>> 8);
data[length++] = (byte) i;
this.length = length;
return this;
}
/**
* Puts a long into this byte vector. The byte vector is automatically
* enlarged if necessary.
*
* @param l a long.
* @return this byte vector.
*/
public ByteVector putLong(final long l){
int length = this.length;
if(length + 8 > data.length)
180 CHAPTER 7. JVM/CLOJURE/ASM/
{
enlarge(8);
}
byte[] data = this.data;
int i = (int) (l >>> 32);
data[length++] = (byte) (i >>> 24);
data[length++] = (byte) (i >>> 16);
data[length++] = (byte) (i >>> 8);
data[length++] = (byte) i;
i = (int) l;
data[length++] = (byte) (i >>> 24);
data[length++] = (byte) (i >>> 16);
data[length++] = (byte) (i >>> 8);
data[length++] = (byte) i;
this.length = length;
return this;
}
/**
* Puts an UTF8 string into this byte vector. The byte vector is
* automatically enlarged if necessary.
*
* @param s a String.
* @return this byte vector.
*/
public ByteVector putUTF8(final String s){
int charLength = s.length();
if(length + 2 + charLength > data.length)
{
enlarge(2 + charLength);
}
int len = length;
byte[] data = this.data;
// optimistic algorithm: instead of computing the byte length
// and then serializing the string (which requires two loops),
// we assume the byte length is equal to char length (which is
// the most frequent case), and we start serializing the string
// right away. During the serialization, if we find that this
// assumption is wrong, we continue with the general method.
data[len++] = (byte) (charLength >>> 8);
data[len++] = (byte) charLength;
for(int i = 0; i < charLength; ++i)
{
char c = s.charAt(i);
if(c >= \001 && c <= \177)
{
data[len++] = (byte) c;
}
else
{
7.4. BYTEVECTOR.JAVA 181
int byteLength = i;
for(int j = i; j < charLength; ++j)
{
c = s.charAt(j);
if(c >= \001 && c <= \177)
{
byteLength++;
}
else if(c > \u07FF)
{
byteLength += 3;
}
else
{
byteLength += 2;
}
}
data[length] = (byte) (byteLength >>> 8);
data[length + 1] = (byte) byteLength;
if(length + 2 + byteLength > data.length)
{
length = len;
enlarge(2 + byteLength);
data = this.data;
}
for(int j = i; j < charLength; ++j)
{
c = s.charAt(j);
if(c >= \001 && c <= \177)
{
data[len++] = (byte) c;
}
else if(c > \u07FF)
{
data[len++] =
(byte) (0xE0 | c >> 12 & 0xF);
data[len++] =
(byte) (0x80 | c >> 6 & 0x3F);
data[len++] =
(byte) (0x80 | c & 0x3F);
}
else
{
data[len++] =
(byte) (0xC0 | c >> 6 & 0x1F);
data[len++] =
(byte) (0x80 | c & 0x3F);
}
}
break;
182 CHAPTER 7. JVM/CLOJURE/ASM/
}
}
length = len;
return this;
}
/**
* Puts an array of bytes into this byte vector. The byte vector is
* automatically enlarged if necessary.
*
* @param b an array of bytes. May be <tt>null</tt> to put <tt>len</tt>
* null bytes into this byte vector.
* @param off index of the fist byte of b that must be copied.
* @param len number of bytes of b that must be copied.
* @return this byte vector.
*/
public ByteVector putByteArray(final byte[] b, final int off,
final int len){
if(length + len > data.length)
{
enlarge(len);
}
if(b != null)
{
System.arraycopy(b, off, data, length, len);
}
length += len;
return this;
}
/**
* Enlarge this byte vector so that it can receive n more bytes.
*
* @param size number of additional bytes that this byte vector should be
* able to receive.
*/
private void enlarge(final int size){
int length1 = 2 * data.length;
int length2 = length + size;
byte[] newData = new byte[length1 > length2 ? length1 : length2];
System.arraycopy(data, 0, newData, 0, length);
data = newData;
}
}
-
7.5. CLASSADAPTER.JAVA 183
7.5 ClassAdapter.java
(ClassVisitor [229])
ClassAdapter.java
/**
* An empty {@link ClassVisitor} that delegates to another
* {@link ClassVisitor}. This class can be used as a super class to
* quickly implement usefull class adapter classes, just by overriding
* the necessary methods.
*
* @author Eric Bruneton
*/
public class ClassAdapter implements ClassVisitor{
/**
* The {@link ClassVisitor} to which this adapter delegates calls.
*/
protected ClassVisitor cv;
/**
* Constructs a new {@link ClassAdapter} object.
*
* @param cv the class visitor to which this adapter must delegate calls.
*/
public ClassAdapter(final ClassVisitor cv){
this.cv = cv;
}
-
7.6. CLASSREADER.JAVA 185
7.6 ClassReader.java
ClassReader.java
import java.io.InputStream;
import java.io.IOException;
/**
* A Java class parser to make a {@link ClassVisitor} visit an
* existing class. This class parses a byte array conforming to
* the Java class file format and calls the appropriate visit
* methods of a given class visitor for each field, method and
* bytecode instruction encountered.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class ClassReader{
/**
* Flag to skip method code. If this class is set <code>CODE</code>
* attribute wont be visited. This can be used, for example, to retrieve
* annotations for methods and method parameters.
*/
public final static int SKIP_CODE = 1;
/**
* Flag to skip the debug information in the class. If this flag is set
* the debug information of the class is not visited, i.e. the
* {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
* {@link MethodVisitor#visitLineNumber visitLineNumber} methods
* will not be called.
*/
public final static int SKIP_DEBUG = 2;
/**
* Flag to skip the stack map frames in the class. If this flag is set
* the stack map frames of the class is not visited, i.e. the
* {@link MethodVisitor#visitFrame visitFrame} method will not be called.
* This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option
* is used: it avoids visiting frames that will be ignored and
* recomputed from scratch in the class writer.
*/
public final static int SKIP_FRAMES = 4;
186 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Flag to expand the stack map frames. By default stack map frames are
* visited in their original format (i.e. "expanded" for classes whose
* version is less than V1_6, and "compressed" for the other classes).
* If this flag is set, stack map frames are always visited in
* expanded format (this option adds a decompression/recompression
* step in ClassReader and ClassWriter which degrades performances
* quite a lot).
*/
public final static int EXPAND_FRAMES = 8;
/**
* The class to be parsed. <i>The content of this array must not be
* modified. This field is intended for {@link Attribute} sub classes,
* and is normally not needed by class generators or adapters.</i>
*/
public final byte[] b;
/**
* The start index of each constant pool item in {@link #b b}, plus one.
* The one byte offset skips the constant pool item tag that indicates
* its type.
*/
private final int[] items;
/**
* The String objects corresponding to the CONSTANT_Utf8 items.
* This cache avoids multiple parsing of a given CONSTANT_Utf8
* constant pool item, which GREATLY improves performances (by a
* factor 2 to 3). This caching strategy could be extended to all
* constant pool items, but its benefit would not be so great for
* these items (because they are much less expensive to parse than
* CONSTANT_Utf8 items).
*/
private final String[] strings;
/**
* Maximum length of the strings contained in the constant pool of the
* class.
*/
private final int maxStringLength;
/**
* Start index of the class header information (access, name...) in
* {@link #b b}.
*/
public final int header;
// -------------------------------------------------------------------
// Constructors
7.6. CLASSREADER.JAVA 187
// ------------------------------------------------------------------
/**
* Constructs a new {@link ClassReader} object.
*
* @param b the bytecode of the class to be read.
*/
public ClassReader(final byte[] b){
this(b, 0, b.length);
}
/**
* Constructs a new {@link ClassReader} object.
*
* @param b the bytecode of the class to be read.
* @param off the start offset of the class data.
* @param len the length of the class data.
*/
public ClassReader(final byte[] b, final int off, final int len){
this.b = b;
// parses the constant pool
items = new int[readUnsignedShort(off + 8)];
int n = items.length;
strings = new String[n];
int max = 0;
int index = off + 10;
for(int i = 1; i < n; ++i)
{
items[i] = index + 1;
int size;
switch(b[index])
{
case ClassWriter.FIELD:
case ClassWriter.METH:
case ClassWriter.IMETH:
case ClassWriter.INT:
case ClassWriter.FLOAT:
case ClassWriter.NAME_TYPE:
size = 5;
break;
case ClassWriter.LONG:
case ClassWriter.DOUBLE:
size = 9;
++i;
break;
case ClassWriter.UTF8:
size = 3 + readUnsignedShort(index + 1);
if(size > max)
{
max = size;
188 CHAPTER 7. JVM/CLOJURE/ASM/
}
break;
// case ClassWriter.CLASS:
// case ClassWriter.STR:
default:
size = 3;
break;
}
index += size;
}
maxStringLength = max;
// the class header information starts just after the constant pool
header = index;
}
/**
* Returns the classs access flags (see {@link Opcodes}). This value may
* not reflect Deprecated and Synthetic flags when bytecode is before 1.5
* and those flags are represented by attributes.
*
* @return the class access flags
* @see ClassVisitor#visit(int,int,String,String,String,String[])
*/
public int getAccess(){
return readUnsignedShort(header);
}
/**
* Returns the internal name of the class (see
* {@link Type#getInternalName() getInternalName}).
*
* @return the internal class name
* @see ClassVisitor#visit(int,int,String,String,String,String[])
*/
public String getClassName(){
return readClass(header + 2, new char[maxStringLength]);
}
/**
* Returns the internal of name of the super class (see
* {@link Type#getInternalName() getInternalName}). For interfaces, the
* super class is {@link Object}.
*
* @return the internal name of super class, or <tt>null</tt> for
* {@link Object} class.
* @see ClassVisitor#visit(int,int,String,String,String,String[])
*/
public String getSuperName(){
int n = items[readUnsignedShort(header + 4)];
return n == 0 ? null : readUTF8(n, new char[maxStringLength]);
7.6. CLASSREADER.JAVA 189
/**
* Returns the internal names of the classs interfaces (see
* {@link Type#getInternalName() getInternalName}).
*
* @return the array of internal names for all implemented interfaces or
* <tt>null</tt>.
* @see ClassVisitor#visit(int,int,String,String,String,String[])
*/
public String[] getInterfaces(){
int index = header + 6;
int n = readUnsignedShort(index);
String[] interfaces = new String[n];
if(n > 0)
{
char[] buf = new char[maxStringLength];
for(int i = 0; i < n; ++i)
{
index += 2;
interfaces[i] = readClass(index, buf);
}
}
return interfaces;
}
/**
* Copies the constant pool data into the given {@link ClassWriter}.
* Should be called before the {@link #accept(ClassVisitor,int)} method.
*
* @param classWriter the {@link ClassWriter} to copy constant pool into.
*/
void copyPool(final ClassWriter classWriter){
char[] buf = new char[maxStringLength];
int ll = items.length;
Item[] items2 = new Item[ll];
for(int i = 1; i < ll; i++)
{
int index = items[i];
int tag = b[index - 1];
Item item = new Item(i);
int nameType;
switch(tag)
{
case ClassWriter.FIELD:
case ClassWriter.METH:
case ClassWriter.IMETH:
nameType = items[readUnsignedShort(index + 2)];
item.set(tag,
readClass(index, buf),
190 CHAPTER 7. JVM/CLOJURE/ASM/
readUTF8(nameType, buf),
readUTF8(nameType + 2, buf));
break;
case ClassWriter.INT:
item.set(readInt(index));
break;
case ClassWriter.FLOAT:
item.set(Float.intBitsToFloat(readInt(index)));
break;
case ClassWriter.NAME_TYPE:
item.set(tag,
readUTF8(index, buf),
readUTF8(index + 2, buf),
null);
break;
case ClassWriter.LONG:
item.set(readLong(index));
++i;
break;
case ClassWriter.DOUBLE:
item.set(Double.longBitsToDouble(readLong(index)));
++i;
break;
case ClassWriter.UTF8:
{
String s = strings[i];
if(s == null)
{
index = items[i];
s = strings[i] = readUTF(index + 2,
readUnsignedShort(index),
buf);
}
item.set(tag, s, null, null);
}
break;
// case ClassWriter.STR:
// case ClassWriter.CLASS:
default:
item.set(tag, readUTF8(index, buf), null, null);
break;
}
7.6. CLASSREADER.JAVA 191
/**
* Constructs a new {@link ClassReader} object.
*
* @param is an input stream from which to read the class.
* @throws IOException if a problem occurs during reading.
*/
public ClassReader(final InputStream is) throws IOException{
this(readClass(is));
}
/**
* Constructs a new {@link ClassReader} object.
*
* @param name the fully qualified name of the class to be read.
* @throws IOException if an exception occurs during reading.
*/
public ClassReader(final String name) throws IOException{
this(ClassLoader.getSystemResourceAsStream(name.replace(., /)
+ ".class"));
}
/**
* Reads the bytecode of a class.
*
* @param is an input stream from which to read the class.
* @return the bytecode read from the given input stream.
* @throws IOException if a problem occurs during reading.
*/
private static byte[] readClass(final InputStream is) throws IOException{
if(is == null)
{
throw new IOException("Class not found");
}
byte[] b = new byte[is.available()];
int len = 0;
while(true)
{
int n = is.read(b, len, b.length - len);
192 CHAPTER 7. JVM/CLOJURE/ASM/
if(n == -1)
{
if(len < b.length)
{
byte[] c = new byte[len];
System.arraycopy(b, 0, c, 0, len);
b = c;
}
return b;
}
len += n;
if(len == b.length)
{
byte[] c = new byte[b.length + 1000];
System.arraycopy(b, 0, c, 0, len);
b = c;
}
}
}
// -------------------------------------------------------------------
// Public methods
// -------------------------------------------------------------------
/**
* Makes the given visitor visit the Java class of this
* {@link ClassReader}. This class is the one specified in the
* constructor (see {@link #ClassReader(byte[]) ClassReader}).
*
* @param classVisitor the visitor that must visit this class.
* @param flags option flags that can be used to modify
* the default behavior of this class. See
* {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}.
*/
public void accept(final ClassVisitor classVisitor, final int flags){
accept(classVisitor, new Attribute[0], flags);
}
/**
* Makes the given visitor visit the Java class of this
* {@link ClassReader}. This class is the one specified in the
* constructor (see {@link #ClassReader(byte[]) ClassReader}).
*
* @param classVisitor the visitor that must visit this class.
* @param attrs prototypes of the attributes that must be parsed
* during the visit of the class. Any attribute whose
* type is not equal to the type of one the
* prototypes will not be parsed: its byte array
* value will be passed unchanged to the ClassWriter.
* <i>This may corrupt it if this value contains
7.6. CLASSREADER.JAVA 193
int access;
String name;
String desc;
String attrName;
String signature;
int anns = 0;
int ianns = 0;
Attribute cattrs = null;
i = readUnsignedShort(v);
v += 2;
for(; i > 0; --i)
{
attrName = readUTF8(v, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if(attrName.equals("SourceFile"))
{
sourceFile = readUTF8(v + 6, c);
}
else if(attrName.equals("InnerClasses"))
{
w = v + 6;
}
else if(attrName.equals("EnclosingMethod"))
{
enclosingOwner = readClass(v + 6, c);
int item = readUnsignedShort(v + 8);
if(item != 0)
{
enclosingName = readUTF8(items[item], c);
7.6. CLASSREADER.JAVA 195
superClassName,
implementedItfs);
readUnsignedShort(w) == 0
? null
: readClass(w, c), readUnsignedShort(w + 2) == 0
? null
: readClass(w + 2, c), readUnsignedShort(w + 4) == 0
? null
: readUTF8(w + 4, c),
readUnsignedShort(w + 6));
w += 8;
}
}
j = readUnsignedShort(u + 6);
u += 8;
for(; j > 0; --j)
{
attrName = readUTF8(u, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if(attrName.equals("ConstantValue"))
{
fieldValueItem = readUnsignedShort(u + 6);
}
else if(attrName.equals("Signature"))
{
signature = readUTF8(u + 6, c);
}
else if(attrName.equals("Deprecated"))
{
access |= Opcodes.ACC_DEPRECATED;
}
else if(attrName.equals("Synthetic"))
{
access |= Opcodes.ACC_SYNTHETIC;
198 CHAPTER 7. JVM/CLOJURE/ASM/
}
else if(attrName.equals("RuntimeVisibleAnnotations"))
{
anns = u + 6;
}
else if(attrName.equals("RuntimeInvisibleAnnotations"))
{
ianns = u + 6;
}
else
{
attr = readAttribute(attrs,
attrName,
u + 6,
readInt(u + 2),
c,
-1,
null);
if(attr != null)
{
attr.next = cattrs;
cattrs = attr;
}
}
u += 6 + readInt(u + 2);
}
// visits the field
FieldVisitor fv =
classVisitor.visitField(access, name, desc, signature,
fieldValueItem == 0 ? null : readConst(fieldValueItem, c));
// visits the field annotations and attributes
if(fv != null)
{
for(j = 1; j >= 0; --j)
{
v = j == 0 ? ianns : anns;
if(v != 0)
{
k = readUnsignedShort(v);
v += 2;
for(; k > 0; --k)
{
v = readAnnotationValues(v + 2, c, true,
fv.visitAnnotation(readUTF8(v, c), j != 0));
}
}
}
while(cattrs != null)
{
attr = cattrs.next;
7.6. CLASSREADER.JAVA 199
cattrs.next = null;
fv.visitAttribute(cattrs);
cattrs = attr;
}
fv.visitEnd();
}
}
String[] exceptions;
if(w == 0)
{
exceptions = null;
}
else
{
exceptions = new String[readUnsignedShort(w)];
w += 2;
for(j = 0; j < exceptions.length; ++j)
{
exceptions[j] = readClass(w, c);
w += 2;
}
}
if(mv != null)
{
/*
* if the returned MethodVisitor is in fact a MethodWriter, it
* means there is no method adapter between the reader and the
* writer. If, in addition, the writers constant pool was
* copied from this reader (mw.cw.cr == this), and the
* signature and exceptions of the method have not been
* changed, then it is possible to skip all visit events and
* just copy the original code of the method to the writer
* (the access, name and descriptor can have been changed,
* this is not important since they are not copied as is from
* the reader).
*/
if(mv instanceof MethodWriter)
{
MethodWriter mw = (MethodWriter) mv;
if(mw.cw.cr == this)
{
if(signature == mw.signature)
{
boolean sameExceptions = false;
if(exceptions == null)
{
sameExceptions = mw.exceptionCount == 0;
}
else
202 CHAPTER 7. JVM/CLOJURE/ASM/
{
if(exceptions.length == mw.exceptionCount)
{
sameExceptions = true;
for(j = exceptions.length - 1; j >= 0; --j)
{
w -= 2;
if(mw.exceptions[j] !=
readUnsignedShort(w))
{
sameExceptions = false;
break;
}
}
}
}
if(sameExceptions)
{
/*
* we do not copy directly the code into
* MethodWriter to save a byte array copy
* operation. The real copy will be done in
* ClassWriter.toByteArray().
*/
mw.classReaderOffset = u0;
mw.classReaderLength = u - u0;
continue;
}
}
}
}
if(dann != 0)
{
AnnotationVisitor dv = mv.visitAnnotationDefault();
readAnnotationValue(dann, c, null, dv);
if(dv != null)
{
dv.visitEnd();
}
}
for(j = 1; j >= 0; --j)
{
w = j == 0 ? ianns : anns;
if(w != 0)
{
k = readUnsignedShort(w);
w += 2;
for(; k > 0; --k)
{
7.6. CLASSREADER.JAVA 203
w = readAnnotationValues(w + 2, c, true,
mv.visitAnnotation(readUTF8(w, c), j != 0));
}
}
}
if(mpanns != 0)
{
readParameterAnnotations(mpanns, c, true, mv);
}
if(impanns != 0)
{
readParameterAnnotations(impanns, c, false, mv);
}
while(cattrs != null)
{
attr = cattrs.next;
cattrs.next = null;
mv.visitAttribute(cattrs);
cattrs = attr;
}
}
int codeStart = v;
int codeEnd = v + codeLength;
mv.visitCode();
label = w + readInt(v);
if(labels[label] == null)
{
labels[label] = new Label();
}
j = readInt(v + 4);
v += 8;
for(; j > 0; --j)
{
label = w + readInt(v + 4);
v += 8;
if(labels[label] == null)
{
labels[label] = new Label();
}
}
break;
case ClassWriter.VAR_INSN:
case ClassWriter.SBYTE_INSN:
case ClassWriter.LDC_INSN:
v += 2;
break;
case ClassWriter.SHORT_INSN:
case ClassWriter.LDCW_INSN:
case ClassWriter.FIELDORMETH_INSN:
case ClassWriter.TYPE_INSN:
case ClassWriter.IINC_INSN:
v += 3;
break;
case ClassWriter.ITFMETH_INSN:
v += 5;
break;
// case MANA_INSN:
default:
v += 4;
break;
}
}
// parses the try catch entries
j = readUnsignedShort(v);
v += 2;
for(; j > 0; --j)
{
label = readUnsignedShort(v);
Label start = labels[label];
if(start == null)
{
labels[label] = start = new Label();
}
label = readUnsignedShort(v + 2);
206 CHAPTER 7. JVM/CLOJURE/ASM/
k = readUnsignedShort(v + 6);
w = v + 8;
for(; k > 0; --k)
{
label = readUnsignedShort(w);
if(labels[label] == null)
{
labels[label] = new Label(true);
}
label += readUnsignedShort(w + 2);
if(labels[label] == null)
{
labels[label] = new Label(true);
}
w += 10;
}
}
}
else if(attrName.equals("LocalVariableTypeTable"))
{
varTypeTable = v + 6;
}
else if(attrName.equals("LineNumberTable"))
{
if(!skipDebug)
{
k = readUnsignedShort(v + 6);
w = v + 8;
for(; k > 0; --k)
{
label = readUnsignedShort(w);
if(labels[label] == null)
{
labels[label] = new Label(true);
}
labels[label].line=readUnsignedShort(w + 2);
w += 4;
}
}
}
else if(attrName.equals("StackMapTable"))
{
if((flags & SKIP_FRAMES) == 0)
{
stackMap = v + 8;
frameCount = readUnsignedShort(v + 6);
}
/*
* here we do not extract the labels corresponding to
* the attribute content. This would require a full
208 CHAPTER 7. JVM/CLOJURE/ASM/
}
if(desc.charAt(j) == L)
{
++j;
while(desc.charAt(j) != ;)
{
++j;
}
}
frameLocal[local++] =
desc.substring(k, ++j);
break;
caseL:
while(desc.charAt(j) != ;)
{
++j;
}
frameLocal[local++] =
desc.substring(k + 1, j++);
break;
default:
break loop;
}
}
frameLocalCount = local;
}
/*
* for the first explicit frame the offset is not
* offset_delta + 1 but only offset_delta; setting the
* implicit frame offset to -1 allow the use of the
* "offset_delta + 1" rule in all cases
*/
frameOffset = -1;
}
v = codeStart;
Label l;
while(v < codeEnd)
{
w = v - codeStart;
l = labels[w];
if(l != null)
{
mv.visitLabel(l);
if(!skipDebug && l.line > 0)
{
mv.visitLineNumber(l.line, l);
}
}
7.6. CLASSREADER.JAVA 211
while(frameLocal != null
&& (frameOffset == w || frameOffset == -1))
{
// if there is a frame for this offset,
// makes the visitor visit it,
// and reads the next frame if there is one.
if(!zip || unzip)
{
mv.visitFrame(Opcodes.F_NEW,
frameLocalCount,
frameLocal,
frameStackCount,
frameStack);
}
else if(frameOffset != -1)
{
mv.visitFrame(frameMode,
frameLocalDiff,
frameLocal,
frameStackCount,
frameStack);
}
if(frameCount > 0)
{
int tag, delta, n;
if(zip)
{
tag = b[stackMap++] & 0xFF;
}
else
{
tag = MethodWriter.FULL_FRAME;
frameOffset = -1;
}
frameLocalDiff = 0;
if(tag <
MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME)
{
delta = tag;
frameMode = Opcodes.F_SAME;
frameStackCount = 0;
}
else if(tag < MethodWriter.RESERVED)
{
delta = tag
- MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
stackMap = readFrameType(frameStack,
0,
stackMap,
212 CHAPTER 7. JVM/CLOJURE/ASM/
c,
labels);
frameMode = Opcodes.F_SAME1;
frameStackCount = 1;
}
else
{
delta = readUnsignedShort(stackMap);
stackMap += 2;
if(tag ==
MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
{
stackMap = readFrameType(frameStack,
0,
stackMap,
c,
labels);
frameMode = Opcodes.F_SAME1;
frameStackCount = 1;
}
else if(tag >= MethodWriter.CHOP_FRAME &&
tag < MethodWriter.SAME_FRAME_EXTENDED)
{
frameMode = Opcodes.F_CHOP;
frameLocalDiff =
MethodWriter.SAME_FRAME_EXTENDED
- tag;
frameLocalCount -= frameLocalDiff;
frameStackCount = 0;
}
else if(tag ==
MethodWriter.SAME_FRAME_EXTENDED)
{
frameMode = Opcodes.F_SAME;
frameStackCount = 0;
}
else if(tag < MethodWriter.FULL_FRAME)
{
j = unzip ? frameLocalCount : 0;
for(k = tag
- MethodWriter.SAME_FRAME_EXTENDED;
k > 0; k--)
{
stackMap = readFrameType(frameLocal,
j++,
stackMap,
c,
labels);
}
frameMode = Opcodes.F_APPEND;
7.6. CLASSREADER.JAVA 213
frameLocalDiff = tag
- MethodWriter.SAME_FRAME_EXTENDED;
frameLocalCount += frameLocalDiff;
frameStackCount = 0;
}
else
{ // if (tag == FULL_FRAME) {
frameMode = Opcodes.F_FULL;
n = frameLocalDiff
= frameLocalCount
= readUnsignedShort(stackMap);
stackMap += 2;
for(j = 0; n > 0; n--)
{
stackMap = readFrameType(frameLocal,
j++,
stackMap,
c,
labels);
}
n = frameStackCount
= readUnsignedShort(stackMap);
stackMap += 2;
for(j = 0; n > 0; n--)
{
stackMap = readFrameType(frameStack,
j++,
stackMap,
c,
labels);
}
}
}
frameOffset += delta + 1;
if(labels[frameOffset] == null)
{
labels[frameOffset] = new Label();
}
--frameCount;
}
else
{
frameLocal = null;
}
}
case ClassWriter.NOARG_INSN:
mv.visitInsn(opcode);
v += 1;
break;
case ClassWriter.IMPLVAR_INSN:
if(opcode > Opcodes.ISTORE)
{
opcode -= 59; // ISTORE_0
mv.visitVarInsn(
Opcodes.ISTORE + (opcode >> 2),
opcode & 0x3);
}
else
{
opcode -= 26; // ILOAD_0
mv.visitVarInsn(
Opcodes.ILOAD + (opcode >> 2),
opcode & 0x3);
}
v += 1;
break;
case ClassWriter.LABEL_INSN:
mv.visitJumpInsn(opcode,
labels[w + readShort(v + 1)]);
v += 3;
break;
case ClassWriter.LABELW_INSN:
mv.visitJumpInsn(opcode - 33,
labels[w + readInt(v + 1)]);
v += 5;
break;
case ClassWriter.WIDE_INSN:
opcode = b[v + 1] & 0xFF;
if(opcode == Opcodes.IINC)
{
mv.visitIincInsn(readUnsignedShort(v + 2),
readShort(v + 4));
v += 6;
}
else
{
mv.visitVarInsn(opcode,
readUnsignedShort(v + 2));
v += 4;
}
break;
case ClassWriter.TABL_INSN:
// skips 0 to 3 padding bytes
v = v + 4 - (w & 3);
// reads instruction
7.6. CLASSREADER.JAVA 215
label = w + readInt(v);
int min = readInt(v + 4);
int max = readInt(v + 8);
v += 12;
Label[] table = new Label[max - min + 1];
for(j = 0; j < table.length; ++j)
{
table[j] = labels[w + readInt(v)];
v += 4;
}
mv.visitTableSwitchInsn(min,
max,
labels[label],
table);
break;
case ClassWriter.LOOK_INSN:
// skips 0 to 3 padding bytes
v = v + 4 - (w & 3);
// reads instruction
label = w + readInt(v);
j = readInt(v + 4);
v += 8;
int[] keys = new int[j];
Label[] values = new Label[j];
for(j = 0; j < keys.length; ++j)
{
keys[j] = readInt(v);
values[j] = labels[w + readInt(v + 4)];
v += 8;
}
mv.visitLookupSwitchInsn(labels[label],
keys,
values);
break;
case ClassWriter.VAR_INSN:
mv.visitVarInsn(opcode, b[v + 1] & 0xFF);
v += 2;
break;
case ClassWriter.SBYTE_INSN:
mv.visitIntInsn(opcode, b[v + 1]);
v += 2;
break;
case ClassWriter.SHORT_INSN:
mv.visitIntInsn(opcode, readShort(v + 1));
v += 3;
break;
case ClassWriter.LDC_INSN:
mv.visitLdcInsn(readConst(b[v + 1] & 0xFF, c));
v += 2;
break;
216 CHAPTER 7. JVM/CLOJURE/ASM/
case ClassWriter.LDCW_INSN:
mv.visitLdcInsn(
readConst(readUnsignedShort(v + 1), c));
v += 3;
break;
case ClassWriter.FIELDORMETH_INSN:
case ClassWriter.ITFMETH_INSN:
int cpIndex = items[readUnsignedShort(v + 1)];
String iowner = readClass(cpIndex, c);
cpIndex = items[readUnsignedShort(cpIndex + 2)];
String iname = readUTF8(cpIndex, c);
String idesc = readUTF8(cpIndex + 2, c);
if(opcode < Opcodes.INVOKEVIRTUAL)
{
mv.visitFieldInsn(opcode,iowner,iname,idesc);
}
else
{
mv.visitMethodInsn(opcode,iowner,iname,idesc);
}
if(opcode == Opcodes.INVOKEINTERFACE)
{
v += 5;
}
else
{
v += 3;
}
break;
case ClassWriter.TYPE_INSN:
mv.visitTypeInsn(opcode, readClass(v + 1, c));
v += 3;
break;
case ClassWriter.IINC_INSN:
mv.visitIincInsn(b[v + 1] & 0xFF, b[v + 2]);
v += 3;
break;
// case MANA_INSN:
default:
mv.visitMultiANewArrayInsn(readClass(v + 1, c),
b[v + 3] & 0xFF);
v += 4;
break;
}
}
l = labels[codeEnd - codeStart];
if(l != null)
{
mv.visitLabel(l);
}
7.6. CLASSREADER.JAVA 217
cattrs.next = null;
mv.visitAttribute(cattrs);
cattrs = attr;
}
// visits the max stack and max locals values
mv.visitMaxs(maxStack, maxLocals);
}
if(mv != null)
{
mv.visitEnd();
}
}
/**
* Reads parameter annotations and makes the given visitor visit them.
*
* @param v start offset in {@link #b b} of the annotations to
* be read.
* @param buf buffer to be used to call {@link #readUTF8 readUTF8},
* {@link #readClass(int,char[]) readClass} or
* {@link #readConst readConst}.
* @param visible <tt>true</tt> if the annotations to be read are visible
* at runtime.
* @param mv the visitor that must visit the annotations.
*/
private void readParameterAnnotations(
int v,
final char[] buf,
final boolean visible,
final MethodVisitor mv){
int n = b[v++] & 0xFF;
for(int i = 0; i < n; ++i)
{
int j = readUnsignedShort(v);
v += 2;
for(; j > 0; --j)
{
v = readAnnotationValues(v + 2, buf, true,
mv.visitParameterAnnotation(i,readUTF8(v, buf),visible));
}
}
}
/**
* Reads the values of an annotation and makes the given visitor
7.6. CLASSREADER.JAVA 219
* visit them.
*
* @param v the start offset in {@link #b b} of the values to be
* read (including the unsigned short that gives the
* number of values).
* @param buf buffer to be used to call {@link #readUTF8 readUTF8},
* {@link #readClass(int,char[]) readClass} or
* {@link #readConst readConst}.
* @param named if the annotation values are named or not.
* @param av the visitor that must visit the values.
* @return the end offset of the annotation values.
*/
private int readAnnotationValues(
int v,
final char[] buf,
final boolean named,
final AnnotationVisitor av){
int i = readUnsignedShort(v);
v += 2;
if(named)
{
for(; i > 0; --i)
{
v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
}
}
else
{
for(; i > 0; --i)
{
v = readAnnotationValue(v, buf, null, av);
}
}
if(av != null)
{
av.visitEnd();
}
return v;
}
/**
* Reads a value of an annotation and makes the given visitor visit it.
*
* @param v the start offset in {@link #b b} of the value to be
* read (<i>not including the value name constant pool
* index</i>).
* @param buf buffer to be used to call {@link #readUTF8 readUTF8},
* {@link #readClass(int,char[]) readClass} or
* {@link #readConst readConst}.
* @param name the name of the value to be read.
220 CHAPTER 7. JVM/CLOJURE/ASM/
readInt(items[readUnsignedShort(v)]) != 0;
v += 3;
}
av.visit(name, zv);
--v;
break;
caseS:
short[] sv = new short[size];
for(i = 0; i < size; i++)
{
sv[i] =
(short) readInt(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, sv);
--v;
break;
caseC:
char[] cv = new char[size];
for(i = 0; i < size; i++)
{
cv[i] =
(char) readInt(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, cv);
--v;
break;
caseI:
int[] iv = new int[size];
for(i = 0; i < size; i++)
{
iv[i] = readInt(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, iv);
--v;
break;
caseJ:
long[] lv = new long[size];
for(i = 0; i < size; i++)
{
lv[i] = readLong(items[readUnsignedShort(v)]);
v += 3;
}
av.visit(name, lv);
--v;
break;
caseF:
float[] fv = new float[size];
7.6. CLASSREADER.JAVA 223
case 3:
frame[index] = Opcodes.DOUBLE;
break;
case 4:
frame[index] = Opcodes.LONG;
break;
case 5:
frame[index] = Opcodes.NULL;
break;
case 6:
frame[index] = Opcodes.UNINITIALIZED_THIS;
break;
case 7: // Object
frame[index] = readClass(v, buf);
v += 2;
break;
default: // Uninitialized
int offset = readUnsignedShort(v);
if(labels[offset] == null)
{
labels[offset] = new Label();
}
frame[index] = labels[offset];
v += 2;
}
return v;
}
/**
* Reads an attribute in {@link #b b}.
*
* @param attrs prototypes of the attributes that must be parsed during
* the visit of the class. Any attribute whose type is not
* equal to the type of one the prototypes is ignored
* (i.e. an empty {@link Attribute} instance is returned).
* @param type the type of the attribute.
* @param off index of the first byte of the attributes content in
* {@link #b b}. The 6 attribute header bytes, containing
* the type and the length of the attribute, are not taken
* into account here (they have already been read).
* @param len the length of the attributes content.
* @param buf buffer to be used to call {@link #readUTF8 readUTF8},
* {@link #readClass(int,char[]) readClass} or
* {@link #readConst readConst}.
* @param codeOff index of the first byte of codes attribute content in
* {@link #b b}, or -1 if the attribute to be read is not
* a code attribute. The 6 attribute header bytes,
* containing the type and the length of the attribute,
* are not taken into account here.
* @param labels the labels of the methods code, or <tt>null</tt> if
7.6. CLASSREADER.JAVA 225
// -------------------------------------------------------------------
// Utility methods: low level parsing
// -------------------------------------------------------------------
/**
* Returns the start index of the constant pool item in {@link #b b},
* plus one. <i>This method is intended for {@link Attribute} sub
* classes, and is normally not needed by class generators or
* adapters.</i>
*
* @param item the index a constant pool item.
* @return the start index of the constant pool item in {@link #b b},
* plus one.
*/
public int getItem(final int item){
return items[item];
}
/**
* Reads a byte value in {@link #b b}. <i>This method is intended for
* {@link Attribute} sub classes, and is normally not needed by class
* generators or adapters.</i>
*
* @param index the start index of the value to be read in {@link #b b}.
* @return the read value.
*/
public int readByte(final int index){
return b[index] & 0xFF;
226 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Reads an unsigned short value in {@link #b b}. <i>This method is
* intended for {@link Attribute} sub classes, and is normally not
* needed by class generators or adapters.</i>
*
* @param index the start index of the value to be read in {@link #b b}.
* @return the read value.
*/
public int readUnsignedShort(final int index){
byte[] b = this.b;
return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
}
/**
* Reads a signed short value in {@link #b b}. <i>This method is intended
* for {@link Attribute} sub classes, and is normally not needed by class
* generators or adapters.</i>
*
* @param index the start index of the value to be read in {@link #b b}.
* @return the read value.
*/
public short readShort(final int index){
byte[] b = this.b;
return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
}
/**
* Reads a signed int value in {@link #b b}. <i>This method is intended
* for {@link Attribute} sub classes, and is normally not needed by
* class generators or adapters.</i>
*
* @param index the start index of the value to be read in {@link #b b}.
* @return the read value.
*/
public int readInt(final int index){
byte[] b = this.b;
return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
| ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
}
/**
* Reads a signed long value in {@link #b b}. <i>This method is intended
* for {@link Attribute} sub classes, and is normally not needed by class
* generators or adapters.</i>
*
* @param index the start index of the value to be read in {@link #b b}.
* @return the read value.
*/
7.6. CLASSREADER.JAVA 227
/**
* Reads an UTF8 string constant pool item in {@link #b b}. <i>This
* method is intended for {@link Attribute} sub classes, and is normally
* not needed by class generators or adapters.</i>
*
* @param index the start index of an unsigned short value in
* {@link #b b}, whose value is the index of an UTF8
* constant pool item.
* @param buf buffer to be used to read the item. This buffer must be
* sufficiently large. It is not automatically resized.
* @return the String corresponding to the specified UTF8 item.
*/
public String readUTF8(int index, final char[] buf){
int item = readUnsignedShort(index);
String s = strings[item];
if(s != null)
{
return s;
}
index = items[item];
return strings[item] =
readUTF(index + 2, readUnsignedShort(index), buf);
}
/**
* Reads UTF8 string in {@link #b b}.
*
* @param index start offset of the UTF8 string to be read.
* @param utfLen length of the UTF8 string to be read.
* @param buf buffer to be used to read the string. This buffer must
* be sufficiently large. It is not automatically resized.
* @return the String corresponding to the specified UTF8 string.
*/
private String readUTF(int index, final int utfLen, final char[] buf){
int endIndex = index + utfLen;
byte[] b = this.b;
int strLen = 0;
int c, d, e;
while(index < endIndex)
{
c = b[index++] & 0xFF;
switch(c >> 4)
{
case 0:
228 CHAPTER 7. JVM/CLOJURE/ASM/
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
buf[strLen++] = (char) c;
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
d = b[index++];
buf[strLen++] = (char) (((c & 0x1F) << 6) | (d & 0x3F));
break;
default:
// 1110 xxxx 10xx xxxx 10xx xxxx
d = b[index++];
e = b[index++];
buf[strLen++] =
(char) (((c & 0x0F) << 12)
| ((d & 0x3F) << 6) | (e & 0x3F));
break;
}
}
return new String(buf, 0, strLen);
}
/**
* Reads a class constant pool item in {@link #b b}. <i>This method
* is intended for {@link Attribute} sub classes, and is normally not
* needed by class generators or adapters.</i>
*
* @param index the start index of an unsigned short value in
* {@link #b b}, whose value is the index of a class
* constant pool item.
* @param buf buffer to be used to read the item. This buffer must be
* sufficiently large. It is not automatically resized.
* @return the String corresponding to the specified class item.
*/
public String readClass(final int index, final char[] buf){
// computes the start index of the CONSTANT_Class item in b
// and reads the CONSTANT_Utf8 item designated by
// the first two bytes of this CONSTANT_Class item
return readUTF8(items[readUnsignedShort(index)], buf);
}
/**
* Reads a numeric or string constant pool item in {@link #b b}.
7.7. CLASSVISITOR.JAVA 229
7.7 ClassVisitor.java
ClassVisitor.java
/**
* A visitor to visit a Java class. The methods of this interface
* must be called in the following order: <tt>visit</tt>
* [ <tt>visitSource</tt> ] [ <tt>visitOuterClass</tt> ]
230 CHAPTER 7. JVM/CLOJURE/ASM/
* ( <tt>visitAnnotation</tt> | <tt>visitAttribute</tt> )*
* (<tt>visitInnerClass</tt> | <tt>visitField</tt> |
* <tt>visitMethod</tt> )* <tt>visitEnd</tt>.
*
* @author Eric Bruneton
*/
public interface ClassVisitor{
/**
* Visits the header of the class.
*
* @param version the class version.
* @param access the classs access flags (see {@link Opcodes}). This
* parameter also indicates if the class is deprecated.
* @param name the internal name of the class (see
* {@link Type#getInternalName() getInternalName}).
* @param signature the signature of this class. May be <tt>null</tt> if
* the class is not a generic one, and does not extend
* or implement generic classes or interfaces.
* @param superName the internal of name of the super class (see
* {@link Type#getInternalName() getInternalName}).
* For interfaces, the super class is {@link Object}.
* May be <tt>null</tt>, but only for the
* {@link Object} class.
* @param interfaces the internal names of the classs interfaces (see
* {@link Type#getInternalName() getInternalName}).
* May be <tt>null</tt>.
*/
void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces);
/**
* Visits the source of the class.
*
* @param source the name of the source file from which the class was
* compiled. May be <tt>null</tt>.
* @param debug additional debug information to compute the
* correspondance between source and compiled elements of
* the class. May be <tt>null</tt>.
*/
void visitSource(String source, String debug);
/**
* Visits the enclosing class of the class. This method must be called
* only if the class has an enclosing class.
7.7. CLASSVISITOR.JAVA 231
*
* @param owner internal name of the enclosing class of the class.
* @param name the name of the method that contains the class, or
* <tt>null</tt> if the class is not enclosed in a method
* of its enclosing class.
* @param desc the descriptor of the method that contains the class, or
* <tt>null</tt> if the class is not enclosed in a method of
* its enclosing class.
*/
void visitOuterClass(String owner, String name, String desc);
/**
* Visits an annotation of the class.
*
* @param desc the class descriptor of the annotation class.
* @param visible <tt>true</tt> if the annotation is visible at runtime.
* @return a visitor to visit the annotation values, or <tt>null</tt> if
* this visitor is not interested in visiting this annotation.
*/
AnnotationVisitor visitAnnotation(String desc, boolean visible);
/**
* Visits a non standard attribute of the class.
*
* @param attr an attribute.
*/
void visitAttribute(Attribute attr);
/**
* Visits information about an inner class. This inner class is not
* necessarily a member of the class being visited.
*
* @param name the internal name of an inner class (see
* {@link Type#getInternalName() getInternalName}).
* @param outerName the internal name of the class to which the inner
* class belongs (see {@link Type#getInternalName()
* getInternalName}). May be <tt>null</tt> for not
* member classes.
* @param innerName the (simple) name of the inner class inside its
* enclosing class. May be <tt>null</tt> for anonymous
* inner classes.
* @param access the access flags of the inner class as originally
* declared in the enclosing class.
*/
void visitInnerClass(
String name,
String outerName,
String innerName,
int access);
232 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Visits a field of the class.
*
* @param access the fields access flags (see {@link Opcodes}).
* This parameter also indicates if the field is
* synthetic and/or deprecated.
* @param name the fields name.
* @param desc the fields descriptor (see {@link Type Type}).
* @param signature the fields signature. May be <tt>null</tt> if the
* fields type does not use generic types.
* @param value the fields initial value. This parameter, which may
* be <tt>null</tt> if the field does not have an
* initial value, must be an {@link Integer}, a
* {@link Float}, a {@link Long}, a {@link Double} or
* a {@link String} (for <tt>int</tt>, <tt>float</tt>,
* <tt>long</tt> or <tt>String</tt> fields
* respectively). <i>This parameter is only used for
* static fields</i>. Its value is ignored for non
* static fields, which must be initialized through
* bytecode instructions in constructors or methods.
* @return a visitor to visit field annotations and attributes, or
* <tt>null</tt> if this class visitor is not interested in
* visiting these annotations and attributes.
*/
FieldVisitor visitField(
int access,
String name,
String desc,
String signature,
Object value);
/**
* Visits a method of the class. This method <i>must</i> return a new
* {@link MethodVisitor} instance (or <tt>null</tt>) each time it is
* called, i.e., it should not return a previously returned visitor.
*
* @param access the methods access flags (see {@link Opcodes}).
* This parameter also indicates if the method is
* synthetic and/or deprecated.
* @param name the methods name.
* @param desc the methods descriptor (see {@link Type Type}).
* @param signature the methods signature. May be <tt>null</tt> if the
* method parameters, return type and exceptions do not
* use generic types.
* @param exceptions the internal names of the methods exception classes
* (see
* {@link Type#getInternalName() getInternalName}).
* May be <tt>null</tt>.
* @return an object to visit the byte code of the method, or
* <tt>null</tt> if this class visitor is not interested in
7.8. CLASSWRITER.JAVA 233
/**
* Visits the end of the class. This method, which is the last one to be
* called, is used to inform the visitor that all the fields and methods
* of the class have been visited.
*/
void visitEnd();
}
7.8 ClassWriter.java
(ClassVisitor [229])
ClassWriter.java
/**
* A {@link ClassVisitor} that generates classes in bytecode form.
* More precisely this visitor generates a byte array conforming to
* the Java class file format. It can be used alone, to generate a
* Java class "from scratch", or with one or more
* {@link ClassReader ClassReader} and adapter class visitor
* to generate a modified class from one or more existing Java classes.
*
* @author Eric Bruneton
*/
public class ClassWriter implements ClassVisitor{
/**
* Flag to automatically compute the maximum stack size and the
* maximum number of local variables of methods. If this flag is set,
* then the arguments of the {@link MethodVisitor#visitMaxs visitMaxs}
* method of the {@link MethodVisitor} returned by the
* {@link #visitMethod visitMethod} method will be ignored, and
* computed automatically from the signature and the bytecode of
* each method.
*
234 CHAPTER 7. JVM/CLOJURE/ASM/
* @see #ClassWriter(int)
*/
public final static int COMPUTE_MAXS = 1;
/**
* Flag to automatically compute the stack map frames of methods from
* scratch. If this flag is set, then the calls to the
* {@link MethodVisitor#visitFrame} method are ignored, and the stack map
* frames are recomputed from the methods bytecode. The arguments of the
* {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and
* recomputed from the bytecode. In other words, computeFrames implies
* computeMaxs.
*
* @see #ClassWriter(int)
*/
public final static int COMPUTE_FRAMES = 2;
/**
* The type of instructions without any argument.
*/
final static int NOARG_INSN = 0;
/**
* The type of instructions with an signed byte argument.
*/
final static int SBYTE_INSN = 1;
/**
* The type of instructions with an signed short argument.
*/
final static int SHORT_INSN = 2;
/**
* The type of instructions with a local variable index argument.
*/
final static int VAR_INSN = 3;
/**
* The type of instructions with an implicit local variable index
* argument.
*/
final static int IMPLVAR_INSN = 4;
/**
* The type of instructions with a type descriptor argument.
*/
final static int TYPE_INSN = 5;
/**
* The type of field and method invocations instructions.
7.8. CLASSWRITER.JAVA 235
*/
final static int FIELDORMETH_INSN = 6;
/**
* The type of the INVOKEINTERFACE instruction.
*/
final static int ITFMETH_INSN = 7;
/**
* The type of instructions with a 2 bytes bytecode offset label.
*/
final static int LABEL_INSN = 8;
/**
* The type of instructions with a 4 bytes bytecode offset label.
*/
final static int LABELW_INSN = 9;
/**
* The type of the LDC instruction.
*/
final static int LDC_INSN = 10;
/**
* The type of the LDC_W and LDC2_W instructions.
*/
final static int LDCW_INSN = 11;
/**
* The type of the IINC instruction.
*/
final static int IINC_INSN = 12;
/**
* The type of the TABLESWITCH instruction.
*/
final static int TABL_INSN = 13;
/**
* The type of the LOOKUPSWITCH instruction.
*/
final static int LOOK_INSN = 14;
/**
* The type of the MULTIANEWARRAY instruction.
*/
final static int MANA_INSN = 15;
/**
* The type of the WIDE instruction.
236 CHAPTER 7. JVM/CLOJURE/ASM/
*/
final static int WIDE_INSN = 16;
/**
* The instruction types of all JVM opcodes.
*/
static byte[] TYPE;
/**
* The type of CONSTANT_Class constant pool items.
*/
final static int CLASS = 7;
/**
* The type of CONSTANT_Fieldref constant pool items.
*/
final static int FIELD = 9;
/**
* The type of CONSTANT_Methodref constant pool items.
*/
final static int METH = 10;
/**
* The type of CONSTANT_InterfaceMethodref constant pool items.
*/
final static int IMETH = 11;
/**
* The type of CONSTANT_String constant pool items.
*/
final static int STR = 8;
/**
* The type of CONSTANT_Integer constant pool items.
*/
final static int INT = 3;
/**
* The type of CONSTANT_Float constant pool items.
*/
final static int FLOAT = 4;
/**
* The type of CONSTANT_Long constant pool items.
*/
final static int LONG = 5;
/**
* The type of CONSTANT_Double constant pool items.
7.8. CLASSWRITER.JAVA 237
*/
final static int DOUBLE = 6;
/**
* The type of CONSTANT_NameAndType constant pool items.
*/
final static int NAME_TYPE = 12;
/**
* The type of CONSTANT_Utf8 constant pool items.
*/
final static int UTF8 = 1;
/**
* Normal type Item stored in the ClassWriter
* {@link ClassWriter#typeTable}, instead of the constant pool,
* in order to avoid clashes with normal constant pool items in
* the ClassWriter constant pools hash table.
*/
final static int TYPE_NORMAL = 13;
/**
* Uninitialized type Item stored in the ClassWriter
* {@link ClassWriter#typeTable}, instead of the constant pool, in
* order to avoid clashes with normal constant pool items in the
* ClassWriter constant pools hash table.
*/
final static int TYPE_UNINIT = 14;
/**
* Merged type Item stored in the ClassWriter
* {@link ClassWriter#typeTable}, instead of the constant pool, in
* order to avoid clashes with normal constant pool items in the
* ClassWriter constant pools hash table.
*/
final static int TYPE_MERGED = 15;
/**
* The class reader from which this class writer was constructed, if any.
*/
ClassReader cr;
/**
* Minor and major version numbers of the class to be generated.
*/
int version;
/**
* Index of the next item to be added in the constant pool.
*/
238 CHAPTER 7. JVM/CLOJURE/ASM/
int index;
/**
* The constant pool of this class.
*/
ByteVector pool;
/**
* The constant pools hash table data.
*/
Item[] items;
/**
* The threshold of the constant pools hash table.
*/
int threshold;
/**
* A reusable key used to look for items in the {@link #items} hash
* table.
*/
Item key;
/**
* A reusable key used to look for items in the {@link #items} hash
* table.
*/
Item key2;
/**
* A reusable key used to look for items in the {@link #items} hash
* table.
*/
Item key3;
/**
* A type table used to temporarily store internal names that will
* not necessarily be stored in the constant pool. This type table
* is used by the control flow and data flow analysis algorithm
* used to compute stack map frames from scratch. This array
* associates to each index <tt>i</tt> the Item whose index is
* <tt>i</tt>. All Item objects stored in this array are also stored
* in the {@link #items} hash table. These two arrays allow to retrieve
* an Item from its index or, conversly, to get the index of an Item
* from its value. Each Item stores an internal name in its
* {@link Item#strVal1} field.
*/
Item[] typeTable;
/**
7.8. CLASSWRITER.JAVA 239
/**
* The access flags of this class.
*/
private int access;
/**
* The constant pool item that contains the internal name of this
* class.
*/
private int name;
/**
* The internal name of this class.
*/
String thisName;
/**
* The constant pool item that contains the signature of this class.
*/
private int signature;
/**
* The constant pool item that contains the internal name of the super
* class of this class.
*/
private int superName;
/**
* Number of interfaces implemented or extended by this class or
* interface.
*/
private int interfaceCount;
/**
* The interfaces implemented or extended by this class or interface.
* More precisely, this array contains the indexes of the constant
* pool items that contain the internal names of these interfaces.
*/
private int[] interfaces;
/**
* The index of the constant pool item that contains the name of the
* source file from which this class was compiled.
*/
private int sourceFile;
240 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* The SourceDebug attribute of this class.
*/
private ByteVector sourceDebug;
/**
* The constant pool item that contains the name of the enclosing class
* of this class.
*/
private int enclosingMethodOwner;
/**
* The constant pool item that contains the name and descriptor of the
* enclosing method of this class.
*/
private int enclosingMethod;
/**
* The runtime visible annotations of this class.
*/
private AnnotationWriter anns;
/**
* The runtime invisible annotations of this class.
*/
private AnnotationWriter ianns;
/**
* The non standard attributes of this class.
*/
private Attribute attrs;
/**
* The number of entries in the InnerClasses attribute.
*/
private int innerClassesCount;
/**
* The InnerClasses attribute.
*/
private ByteVector innerClasses;
/**
* The fields of this class. These fields are stored in a linked list
* of {@link FieldWriter} objects, linked to each other by their
* {@link FieldWriter#next} field. This field stores the first element
* of this list.
*/
FieldWriter firstField;
7.8. CLASSWRITER.JAVA 241
/**
* The fields of this class. These fields are stored in a linked list
* of {@link FieldWriter} objects, linked to each other by their
* {@link FieldWriter#next} field. This field stores the last element
* of this list.
*/
FieldWriter lastField;
/**
* The methods of this class. These methods are stored in a linked list
* of {@link MethodWriter} objects, linked to each other by their
* {@link MethodWriter#next} field. This field stores the first element
* of this list.
*/
MethodWriter firstMethod;
/**
* The methods of this class. These methods are stored in a linked list
* of {@link MethodWriter} objects, linked to each other by their
* {@link MethodWriter#next} field. This field stores the last element
* of this list.
*/
MethodWriter lastMethod;
/**
* <tt>true</tt> if the maximum stack size and number of local variables
* must be automatically computed.
*/
private boolean computeMaxs;
/**
* <tt>true</tt> if the stack map frames must be recomputed from scratch.
*/
private boolean computeFrames;
/**
* <tt>true</tt> if the stack map tables of this class are invalid. The
* {@link MethodWriter#resizeInstructions} method cannot transform
* existing stack map tables, and so produces potentially invalid
* classes when it is executed. In this case the class is reread
* and rewritten with the {@link #COMPUTE_FRAMES} option (the
* resizeInstructions method can resize stack map tables when this
* option is used).
*/
boolean invalidFrames;
// -------------------------------------------------------------------
// Static initializer
// -------------------------------------------------------------------
242 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Computes the instruction types of JVM opcodes.
*/
static
{
int i;
byte[] b = new byte[220];
String s =
"AAAAAAAAAAAAAAAABCKLLDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD"
+ "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIDNOAA"
+ "AAAAGGGGGGGHAFBFAAFFAAQPIIJJIIIIIIIIIIIIIIIIII";
for(i = 0; i < b.length; ++i)
{
b[i] = (byte) (s.charAt(i) - A);
}
TYPE = b;
// b[i] = FIELDORMETH_INSN;
// }
// b[Constants.INVOKEINTERFACE] = ITFMETH_INSN;
//
// // LABEL(W)_INSN instructions
// for (i = Constants.IFEQ; i <= Constants.JSR; ++i) {
// b[i] = LABEL_INSN;
// }
// b[Constants.IFNULL] = LABEL_INSN;
// b[Constants.IFNONNULL] = LABEL_INSN;
// b[200] = LABELW_INSN; // GOTO_W
// b[201] = LABELW_INSN; // JSR_W
// // temporary opcodes used internally by ASM - see Label and
// MethodWriter
// for (i = 202; i < 220; ++i) {
// b[i] = LABEL_INSN;
// }
//
// // LDC(_W) instructions
// b[Constants.LDC] = LDC_INSN;
// b[19] = LDCW_INSN; // LDC_W
// b[20] = LDCW_INSN; // LDC2_W
//
// // special instructions
// b[Constants.IINC] = IINC_INSN;
// b[Constants.TABLESWITCH] = TABL_INSN;
// b[Constants.LOOKUPSWITCH] = LOOK_INSN;
// b[Constants.MULTIANEWARRAY] = MANA_INSN;
// b[196] = WIDE_INSN; // WIDE
//
// for (i = 0; i < b.length; ++i) {
// System.err.print((char)(A + b[i]));
// }
// System.err.println();
}
// -------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------
/**
* Constructs a new {@link ClassWriter} object.
*
* @param flags option flags that can be used to modify the default
* behavior of this class. See {@link #COMPUTE_MAXS},
* {@link #COMPUTE_FRAMES}.
*/
public ClassWriter(final int flags){
index = 1;
pool = new ByteVector();
244 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Constructs a new {@link ClassWriter} object and enables optimizations
* for "mostly add" bytecode transformations. These optimizations are
* the following:
* <p/>
* <ul> <li>The constant pool from the original class is copied as is
* in the new class, which saves time. New constant pool entries will
* be added at the end if necessary, but unused constant pool entries
* <i>wont be removed</i>.</li> <li>Methods that are not transformed
* are copied as is in the new class, directly from the original class
* bytecode (i.e. without emitting visit events for all the method
* instructions), which saves a <i>lot</i> of time. Untransformed
* methods are detected by the fact that the {@link ClassReader}
* receives {@link MethodVisitor} objects that come from a
* {@link ClassWriter} (and not from a custom {@link ClassAdapter}
* or any other {@link ClassVisitor} instance).</li> </ul>
*
* @param classReader the {@link ClassReader} used to read the original
* class. It will be used to copy the entire
* constant pool from the original class and also
* to copy other fragments of original bytecode
* where applicable.
* @param flags option flags that can be used to modify the
* default behavior of this class. See
* {@link #COMPUTE_MAXS}, {@link #COMPUTE_FRAMES}.
*/
public ClassWriter(final ClassReader classReader, final int flags){
this(flags);
classReader.copyPool(this);
this.cr = classReader;
}
// -------------------------------------------------------------------
// Implementation of the ClassVisitor interface
// -------------------------------------------------------------------
// -------------------------------------------------------------------
// Other public methods
// -------------------------------------------------------------------
/**
* Returns the bytecode of the class that was build with this class
* writer.
*
* @return the bytecode of the class that was build with this class
* writer.
*/
public byte[] toByteArray(){
// computes the real size of the bytecode of this class
int size = 24 + 2 * interfaceCount;
int nbFields = 0;
FieldWriter fb = firstField;
while(fb != null)
{
++nbFields;
size += fb.getSize();
fb = fb.next;
}
int nbMethods = 0;
MethodWriter mb = firstMethod;
while(mb != null)
{
++nbMethods;
size += mb.getSize();
mb = mb.next;
}
int attributeCount = 0;
if(signature != 0)
{
++attributeCount;
size += 8;
newUTF8("Signature");
}
248 CHAPTER 7. JVM/CLOJURE/ASM/
if(sourceFile != 0)
{
++attributeCount;
size += 8;
newUTF8("SourceFile");
}
if(sourceDebug != null)
{
++attributeCount;
size += sourceDebug.length + 4;
newUTF8("SourceDebugExtension");
}
if(enclosingMethodOwner != 0)
{
++attributeCount;
size += 10;
newUTF8("EnclosingMethod");
}
if((access & Opcodes.ACC_DEPRECATED) != 0)
{
++attributeCount;
size += 6;
newUTF8("Deprecated");
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
&& (version & 0xffff) < Opcodes.V1_5)
{
++attributeCount;
size += 6;
newUTF8("Synthetic");
}
if(innerClasses != null)
{
++attributeCount;
size += 8 + innerClasses.length;
newUTF8("InnerClasses");
}
if(anns != null)
{
++attributeCount;
size += 8 + anns.getSize();
newUTF8("RuntimeVisibleAnnotations");
}
if(ianns != null)
{
++attributeCount;
size += 8 + ianns.getSize();
newUTF8("RuntimeInvisibleAnnotations");
}
if(attrs != null)
7.8. CLASSWRITER.JAVA 249
{
attributeCount += attrs.getCount();
size += attrs.getSize(this, null, 0, -1, -1);
}
size += pool.length;
// allocates a byte vector of this size, in order to
// avoid unnecessary arraycopy operations in the
// ByteVector.enlarge() method
ByteVector out = new ByteVector(size);
out.putInt(0xCAFEBABE).putInt(version);
out.putShort(index).putByteArray(pool.data, 0, pool.length);
out.putShort(access).putShort(name).putShort(superName);
out.putShort(interfaceCount);
for(int i = 0; i < interfaceCount; ++i)
{
out.putShort(interfaces[i]);
}
out.putShort(nbFields);
fb = firstField;
while(fb != null)
{
fb.put(out);
fb = fb.next;
}
out.putShort(nbMethods);
mb = firstMethod;
while(mb != null)
{
mb.put(out);
mb = mb.next;
}
out.putShort(attributeCount);
if(signature != 0)
{
out.putShort(newUTF8("Signature"))
.putInt(2)
.putShort(signature);
}
if(sourceFile != 0)
{
out.putShort(newUTF8("SourceFile"))
.putInt(2)
.putShort(sourceFile);
}
if(sourceDebug != null)
{
int len = sourceDebug.length - 2;
out.putShort(newUTF8("SourceDebugExtension"))
.putInt(len);
out.putByteArray(sourceDebug.data, 2, len);
250 CHAPTER 7. JVM/CLOJURE/ASM/
}
if(enclosingMethodOwner != 0)
{
out.putShort(newUTF8("EnclosingMethod"))
.putInt(4);
out.putShort(enclosingMethodOwner)
.putShort(enclosingMethod);
}
if((access & Opcodes.ACC_DEPRECATED) != 0)
{
out.putShort(newUTF8("Deprecated")).putInt(0);
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
&& (version & 0xffff) < Opcodes.V1_5)
{
out.putShort(newUTF8("Synthetic")).putInt(0);
}
if(innerClasses != null)
{
out.putShort(newUTF8("InnerClasses"));
out.putInt(innerClasses.length + 2)
.putShort(innerClassesCount);
out.putByteArray(innerClasses.data,
0,innerClasses.length);
}
if(anns != null)
{
out.putShort(newUTF8("RuntimeVisibleAnnotations"));
anns.put(out);
}
if(ianns != null)
{
out.putShort(newUTF8("RuntimeInvisibleAnnotations"));
ianns.put(out);
}
if(attrs != null)
{
attrs.put(this, null, 0, -1, -1, out);
}
if(invalidFrames)
{
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
new ClassReader(out.data).accept(cw, ClassReader.SKIP_FRAMES);
return cw.toByteArray();
}
return out.data;
}
// -------------------------------------------------------------------
// Utility methods: constant pool management
7.8. CLASSWRITER.JAVA 251
// -------------------------------------------------------------------
/**
* Adds a number or string constant to the constant pool of the class
* being build. Does nothing if the constant pool already contains
* a similar item.
*
* @param cst the value of the constant to be added to the constant
* pool. This parameter must be an {@link Integer},
* a {@link Float}, a {@link Long}, a {@link Double},
* a {@link String} or a {@link Type}.
* @return a new or already existing constant item with the given value.
*/
Item newConstItem(final Object cst){
if(cst instanceof Integer)
{
int val = ((Integer) cst).intValue();
return newInteger(val);
}
else if(cst instanceof Byte)
{
int val = ((Byte) cst).intValue();
return newInteger(val);
}
else if(cst instanceof Character)
{
int val = ((Character) cst).charValue();
return newInteger(val);
}
else if(cst instanceof Short)
{
int val = ((Short) cst).intValue();
return newInteger(val);
}
else if(cst instanceof Boolean)
{
int val = ((Boolean) cst).booleanValue() ? 1 : 0;
return newInteger(val);
}
else if(cst instanceof Float)
{
float val = ((Float) cst).floatValue();
return newFloat(val);
}
else if(cst instanceof Long)
{
long val = ((Long) cst).longValue();
return newLong(val);
}
else if(cst instanceof Double)
252 CHAPTER 7. JVM/CLOJURE/ASM/
{
double val = ((Double) cst).doubleValue();
return newDouble(val);
}
else if(cst instanceof String)
{
return newString((String) cst);
}
else if(cst instanceof Type)
{
Type t = (Type) cst;
return newClassItem(t.getSort() == Type.OBJECT
? t.getInternalName()
: t.getDescriptor());
}
else
{
throw new IllegalArgumentException("value " + cst);
}
}
/**
* Adds a number or string constant to the constant pool of the class
* being build. Does nothing if the constant pool already contains a
* similar item. <i>This method is intended for {@link Attribute}
* sub classes, and is normally not needed by class generators or
* adapters.</i>
*
* @param cst the value of the constant to be added to the constant pool.
* This parameter must be an {@link Integer}, a {@link Float},
* a {@link Long}, a {@link Double} or a {@link String}.
* @return the index of a new or already existing constant item with the
* given value.
*/
public int newConst(final Object cst){
return newConstItem(cst).index;
}
/**
* Adds an UTF8 string to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
* <i>This method is intended for {@link Attribute} sub classes, and
* is normally not needed by class generators or adapters.</i>
*
* @param value the String value.
* @return the index of a new or already existing UTF8 item.
*/
public int newUTF8(final String value){
key.set(UTF8, value, null, null);
Item result = get(key);
7.8. CLASSWRITER.JAVA 253
if(result == null)
{
pool.putByte(UTF8).putUTF8(value);
result = new Item(index++, key);
put(result);
}
return result.index;
}
/**
* Adds a class reference to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
* <i>This method is intended for {@link Attribute} sub classes, and is
* normally not needed by class generators or adapters.</i>
*
* @param value the internal name of the class.
* @return a new or already existing class reference item.
*/
Item newClassItem(final String value){
key2.set(CLASS, value, null, null);
Item result = get(key2);
if(result == null)
{
pool.put12(CLASS, newUTF8(value));
result = new Item(index++, key2);
put(result);
}
return result;
}
/**
* Adds a class reference to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
* <i>This method is intended for {@link Attribute} sub classes, and is
* normally not needed by class generators or adapters.</i>
*
* @param value the internal name of the class.
* @return the index of a new or already existing class reference item.
*/
public int newClass(final String value){
return newClassItem(value).index;
}
/**
* Adds a field reference to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
*
* @param owner the internal name of the fields owner class.
* @param name the fields name.
* @param desc the fields descriptor.
254 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Adds a field reference to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
* <i>This method is intended for {@link Attribute} sub classes, and is
* normally not needed by class generators or adapters.</i>
*
* @param owner the internal name of the fields owner class.
* @param name the fields name.
* @param desc the fields descriptor.
* @return the index of a new or already existing field reference item.
*/
public int newField(final String owner,
final String name,
final String desc){
return newFieldItem(owner, name, desc).index;
}
/**
* Adds a method reference to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
*
* @param owner the internal name of the methods owner class.
* @param name the methods name.
* @param desc the methods descriptor.
* @param itf <tt>true</tt> if <tt>owner</tt> is an interface.
* @return a new or already existing method reference item.
*/
Item newMethodItem(
final String owner,
final String name,
final String desc,
final boolean itf){
int type = itf ? IMETH : METH;
key3.set(type, owner, name, desc);
7.8. CLASSWRITER.JAVA 255
/**
* Adds a method reference to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
* <i>This method is intended for {@link Attribute} sub classes, and is
* normally not needed by class generators or adapters.</i>
*
* @param owner the internal name of the methods owner class.
* @param name the methods name.
* @param desc the methods descriptor.
* @param itf <tt>true</tt> if <tt>owner</tt> is an interface.
* @return the index of a new or already existing method reference item.
*/
public int newMethod(
final String owner,
final String name,
final String desc,
final boolean itf){
return newMethodItem(owner, name, desc, itf).index;
}
/**
* Adds an integer to the constant pool of the class being build. Does
* nothing if the constant pool already contains a similar item.
*
* @param value the int value.
* @return a new or already existing int item.
*/
Item newInteger(final int value){
key.set(value);
Item result = get(key);
if(result == null)
{
pool.putByte(INT).putInt(value);
result = new Item(index++, key);
put(result);
}
return result;
}
/**
256 CHAPTER 7. JVM/CLOJURE/ASM/
* Adds a float to the constant pool of the class being build. Does
* nothing if the constant pool already contains a similar item.
*
* @param value the float value.
* @return a new or already existing float item.
*/
Item newFloat(final float value){
key.set(value);
Item result = get(key);
if(result == null)
{
pool.putByte(FLOAT).putInt(key.intVal);
result = new Item(index++, key);
put(result);
}
return result;
}
/**
* Adds a long to the constant pool of the class being build. Does
* nothing if the constant pool already contains a similar item.
*
* @param value the long value.
* @return a new or already existing long item.
*/
Item newLong(final long value){
key.set(value);
Item result = get(key);
if(result == null)
{
pool.putByte(LONG).putLong(value);
result = new Item(index, key);
put(result);
index += 2;
}
return result;
}
/**
* Adds a double to the constant pool of the class being build. Does
* nothing if the constant pool already contains a similar item.
*
* @param value the double value.
* @return a new or already existing double item.
*/
Item newDouble(final double value){
key.set(value);
Item result = get(key);
if(result == null)
{
7.8. CLASSWRITER.JAVA 257
pool.putByte(DOUBLE).putLong(key.longVal);
result = new Item(index, key);
put(result);
index += 2;
}
return result;
}
/**
* Adds a string to the constant pool of the class being build. Does
* nothing if the constant pool already contains a similar item.
*
* @param value the String value.
* @return a new or already existing string item.
*/
private Item newString(final String value){
key2.set(STR, value, null, null);
Item result = get(key2);
if(result == null)
{
pool.put12(STR, newUTF8(value));
result = new Item(index++, key2);
put(result);
}
return result;
}
/**
* Adds a name and type to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
* <i>This method is intended for {@link Attribute} sub classes, and
* is normally not needed by class generators or adapters.</i>
*
* @param name a name.
* @param desc a type descriptor.
* @return the index of a new or already existing name and type item.
*/
public int newNameType(final String name, final String desc){
key2.set(NAME_TYPE, name, desc, null);
Item result = get(key2);
if(result == null)
{
put122(NAME_TYPE, newUTF8(name), newUTF8(desc));
result = new Item(index++, key2);
put(result);
}
return result.index;
}
/**
258 CHAPTER 7. JVM/CLOJURE/ASM/
* Adds the given internal name to {@link #typeTable} and returns its
* index. Does nothing if the type table already contains this internal
* name.
*
* @param type the internal name to be added to the type table.
* @return the index of this internal name in the type table.
*/
int addType(final String type){
key.set(TYPE_NORMAL, type, null, null);
Item result = get(key);
if(result == null)
{
result = addType(key);
}
return result.index;
}
/**
* Adds the given "uninitialized" type to {@link #typeTable} and returns
* its index. This method is used for UNINITIALIZED types, made of an
* internal name and a bytecode offset.
*
* @param type the internal name to be added to the type table.
* @param offset the bytecode offset of the NEW instruction that created
* this UNINITIALIZED type value.
* @return the index of this internal name in the type table.
*/
int addUninitializedType(final String type, final int offset){
key.type = TYPE_UNINIT;
key.intVal = offset;
key.strVal1 = type;
key.hashCode =
0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset);
Item result = get(key);
if(result == null)
{
result = addType(key);
}
return result.index;
}
/**
* Adds the given Item to {@link #typeTable}.
*
* @param item the value to be added to the type table.
* @return the added Item, which a new Item instance with the same
* value as the given Item.
*/
private Item addType(final Item item){
++typeCount;
7.8. CLASSWRITER.JAVA 259
/**
* Returns the index of the common super type of the two given types.
* This method calls {@link #getCommonSuperClass} and caches the result
* in the {@link #items} hash table to speedup future calls with the
* same parameters.
*
* @param type1 index of an internal name in {@link #typeTable}.
* @param type2 index of an internal name in {@link #typeTable}.
* @return the index of the common super type of the two given types.
*/
int getMergedType(final int type1, final int type2){
key2.type = TYPE_MERGED;
key2.longVal = type1 | (((long) type2) << 32);
key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2);
Item result = get(key2);
if(result == null)
{
String t = typeTable[type1].strVal1;
String u = typeTable[type2].strVal1;
key2.intVal = addType(getCommonSuperClass(t, u));
result = new Item((short) 0, key2);
put(result);
}
return result.intVal;
}
/**
* Returns the common super type of the two given types. The default
* implementation of this method <i>loads<i> the two given classes
* and uses the java.lang.Class methods to find the common super class.
* It can be overriden to compute this common super type in other ways,
* in particular without actually loading any class, or to take into
* account the class that is currently being generated by this
260 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Returns the constant pools hash table item which is equal to the
* given item.
*
* @param key a constant pool item.
* @return the constant pools hash table item which is equal to the
* given item, or <tt>null</tt> if there is no such item.
*/
7.8. CLASSWRITER.JAVA 261
/**
* Puts the given item in the constant pools hash table. The hash
* table <i>must</i> not already contains this item.
*
* @param i the item to be added to the constant pools hash table.
*/
private void put(final Item i){
if(index > threshold)
{
int ll = items.length;
int nl = ll * 2 + 1;
Item[] newItems = new Item[nl];
for(int l = ll - 1; l >= 0; --l)
{
Item j = items[l];
while(j != null)
{
int index = j.hashCode % newItems.length;
Item k = j.next;
j.next = newItems[index];
newItems[index] = j;
j = k;
}
}
items = newItems;
threshold = (int) (nl * 0.75);
}
int index = i.hashCode % items.length;
i.next = items[index];
items[index] = i;
}
/**
* Puts one byte and two shorts into the constant pool.
*
* @param b a byte.
* @param s1 a short.
* @param s2 another short.
*/
private void put122(final int b, final int s1, final int s2){
pool.put12(b, s1).putShort(s2);
262 CHAPTER 7. JVM/CLOJURE/ASM/
}
}
7.9 Edge.java
Edge.java
/**
* An edge in the control flow graph of a method body. See
* {@link Label Label}.
*
* @author Eric Bruneton
*/
class Edge{
/**
* Denotes a normal control flow graph edge.
*/
final static int NORMAL = 0;
/**
* Denotes a control flow graph edge corresponding to an exception
* handler. More precisely any {@link Edge} whose {@link #info} is
* strictly positive corresponds to an exception handler. The actual
* value of {@link #info} is the index, in the {@link ClassWriter}
* type table, of the exception that is catched.
*/
final static int EXCEPTION = 0x7FFFFFFF;
/**
* Information about this control flow graph edge. If
* {@link ClassWriter#COMPUTE_MAXS} is used this field is the
* (relative) stack size in the basic block from which this edge
* originates. This size is equal to the stack size at the "jump"
* instruction to which this edge corresponds, relatively to the
* stack size at the beginning of the originating basic block.
* If {@link ClassWriter#COMPUTE_FRAMES} is used, this field is
* the kind of this control flow graph edge (i.e. NORMAL or EXCEPTION).
*/
int info;
/**
7.10. FIELDVISITOR.JAVA 263
* The successor block of the basic block from which this edge
* originates.
*/
Label successor;
/**
* The next edge in the list of successors of the originating basic
* block. See {@link Label#successors successors}.
*/
Edge next;
}
7.10 FieldVisitor.java
FieldVisitor.java
/**
* A visitor to visit a Java field. The methods of this interface
* must be called in the following order: ( <tt>visitAnnotation</tt> |
* <tt>visitAttribute</tt> )* <tt>visitEnd</tt>.
*
* @author Eric Bruneton
*/
public interface FieldVisitor{
/**
* Visits an annotation of the field.
*
* @param desc the class descriptor of the annotation class.
* @param visible <tt>true</tt> if the annotation is visible at runtime.
* @return a visitor to visit the annotation values, or <tt>null</tt> if
* this visitor is not interested in visiting this annotation.
*/
AnnotationVisitor visitAnnotation(String desc, boolean visible);
/**
* Visits a non standard attribute of the field.
*
* @param attr an attribute.
*/
void visitAttribute(Attribute attr);
264 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Visits the end of the field. This method, which is the last one to be
* called, is used to inform the visitor that all the annotations and
* attributes of the field have been visited.
*/
void visitEnd();
}
7.11 FieldWriter.java
(FieldVisitor [263])
FieldWriter.java
/**
* An {@link FieldVisitor} that generates Java fields in bytecode form.
*
* @author Eric Bruneton
*/
final class FieldWriter implements FieldVisitor{
/**
* Next field writer (see {@link ClassWriter#firstField firstField}).
*/
FieldWriter next;
/**
* The class writer to which this field must be added.
*/
private ClassWriter cw;
/**
* Access flags of this field.
*/
private int access;
/**
* The index of the constant pool item that contains the name of this
* method.
*/
private int name;
/**
* The index of the constant pool item that contains the descriptor of
7.11. FIELDWRITER.JAVA 265
* this field.
*/
private int desc;
/**
* The index of the constant pool item that contains the signature of
* this field.
*/
private int signature;
/**
* The index of the constant pool item that contains the constant value
* of this field.
*/
private int value;
/**
* The runtime visible annotations of this field. May be <tt>null</tt>.
*/
private AnnotationWriter anns;
/**
* The runtime invisible annotations of this field. May be <tt>null</tt>.
*/
private AnnotationWriter ianns;
/**
* The non standard attributes of this field. May be <tt>null</tt>.
*/
private Attribute attrs;
// -------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------
/**
* Constructs a new {@link FieldWriter}.
*
* @param cw the class writer to which this field must be added.
* @param access the fields access flags (see {@link Opcodes}).
* @param name the fields name.
* @param desc the fields descriptor (see {@link Type}).
* @param signature the fields signature. May be <tt>null</tt>.
* @param value the fields constant value. May be <tt>null</tt>.
*/
protected FieldWriter(
final ClassWriter cw,
final int access,
final String name,
final String desc,
266 CHAPTER 7. JVM/CLOJURE/ASM/
// -------------------------------------------------------------------
// Implementation of the FieldVisitor interface
// -------------------------------------------------------------------
attr.next = attrs;
attrs = attr;
}
// -------------------------------------------------------------------
// Utility methods
// -------------------------------------------------------------------
/**
* Returns the size of this field.
*
* @return the size of this field.
*/
int getSize(){
int size = 8;
if(value != 0)
{
cw.newUTF8("ConstantValue");
size += 8;
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
&& (cw.version & 0xffff) < Opcodes.V1_5)
{
cw.newUTF8("Synthetic");
size += 6;
}
if((access & Opcodes.ACC_DEPRECATED) != 0)
{
cw.newUTF8("Deprecated");
size += 6;
}
if(signature != 0)
{
cw.newUTF8("Signature");
size += 8;
}
if(anns != null)
{
cw.newUTF8("RuntimeVisibleAnnotations");
size += 8 + anns.getSize();
}
if(ianns != null)
{
cw.newUTF8("RuntimeInvisibleAnnotations");
size += 8 + ianns.getSize();
}
if(attrs != null)
268 CHAPTER 7. JVM/CLOJURE/ASM/
{
size += attrs.getSize(cw, null, 0, -1, -1);
}
return size;
}
/**
* Puts the content of this field into the given byte vector.
*
* @param out where the content of this field must be put.
*/
void put(final ByteVector out){
out.putShort(access).putShort(name).putShort(desc);
int attributeCount = 0;
if(value != 0)
{
++attributeCount;
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
&& (cw.version & 0xffff) < Opcodes.V1_5)
{
++attributeCount;
}
if((access & Opcodes.ACC_DEPRECATED) != 0)
{
++attributeCount;
}
if(signature != 0)
{
++attributeCount;
}
if(anns != null)
{
++attributeCount;
}
if(ianns != null)
{
++attributeCount;
}
if(attrs != null)
{
attributeCount += attrs.getCount();
}
out.putShort(attributeCount);
if(value != 0)
{
out.putShort(cw.newUTF8("ConstantValue"));
out.putInt(2).putShort(value);
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
7.12. FRAME.JAVA 269
7.12 Frame.java
Frame.java
/**
* Information about the input and output stack map frames of a basic
* block.
*
* @author Eric Bruneton
*/
final class Frame{
270 CHAPTER 7. JVM/CLOJURE/ASM/
/*
* Frames are computed in a two steps process: during the visit
* of each instruction, the state of the frame at the end of
* current basic block is updated by simulating the action of
* the instruction on the previous state of this so called
* "output frame". In visitMaxs, a fix point algorithm is used
* to compute the "input frame" of each basic block, i.e. the
* stack mapframe at the begining of the basic block, starting
* from the input frameof the first basic block (which is
* computed from the method descriptor),and by using the
* previously computed output frames to compute the input
* state of the other blocks.
*
* All output and input frames are stored as arrays of
* integers. Reference and array types are represented by an
* index into a type table (which is not the same as the constant
* pool of the class, in order to avoid adding unnecessary
* constants in the pool - not all computed frames will end up
* being stored in the stack map table). This allows very fast
* type comparisons.
*
* Output stack map frames are computed relatively to the input
* frame of the basic block, which is not yet known when output
* frames are computed. It is therefore necessary to be able to
* represent abstract types such as "the type at position x in
* the input frame locals" or "the type at position x from the
* top of the input frame stack" or even "the type at position
* x in the input frame, with y more (or less) array dimensions".
* This explains the rather complicated type format used in
* output frames.
*
* This format is the following: DIM KIND VALUE (4, 4 and 24
* bits). DIM is a signed number of array dimensions (from -8 to
* 7). KIND is either BASE, LOCAL or STACK. BASE is used for
* types that are not relative to the input frame. LOCAL is used
* for types that are relative to the input local variable types.
* STACK is used for types that are relative to the input stack
* types. VALUE depends on KIND. For LOCAL types, it is an index
* in the input local variable types. For STACK types, it is a
* position relatively to the top of input frame stack. For BASE
* types, it is either one of the constants defined in
* FrameVisitor, or for OBJECT and UNINITIALIZED types, a tag
* and an index in the type table.
*
* Output frames can contain types of any kind and with a
* positive or negative dimension (and even unassigned types,
* represented by 0 - which does not correspond to any valid
* type value). Input frames can only contain BASE types of
* positive or null dimension. In all cases the type table
* contains only internal type names (array type descriptors
7.12. FRAME.JAVA 271
/**
* Mask to get the dimension of a frame type. This dimension is a signed
* integer between -8 and 7.
*/
final static int DIM = 0xF0000000;
/**
* Constant to be added to a type to get a type with one more dimension.
*/
final static int ARRAY_OF = 0x10000000;
/**
* Constant to be added to a type to get a type with one less dimension.
*/
final static int ELEMENT_OF = 0xF0000000;
/**
* Mask to get the kind of a frame type.
*
* @see #BASE
* @see #LOCAL
* @see #STACK
*/
final static int KIND = 0xF000000;
/**
* Mask to get the value of a frame type.
*/
final static int VALUE = 0xFFFFFF;
/**
* Mask to get the kind of base types.
*/
final static int BASE_KIND = 0xFF00000;
/**
* Mask to get the value of base types.
272 CHAPTER 7. JVM/CLOJURE/ASM/
*/
final static int BASE_VALUE = 0xFFFFF;
/**
* Kind of the types that are not relative to an input stack map frame.
*/
final static int BASE = 0x1000000;
/**
* Base kind of the base reference types. The BASE_VALUE of such types
* is an index into the type table.
*/
final static int OBJECT = BASE | 0x700000;
/**
* Base kind of the uninitialized base types. The BASE_VALUE of such
* types in an index into the type table (the Item at that index
* contains both an instruction offset and an internal class name).
*/
final static int UNINITIALIZED = BASE | 0x800000;
/**
* Kind of the types that are relative to the local variable types of
* an input stack map frame. The value of such types is a local
* variable index.
*/
private final static int LOCAL = 0x2000000;
/**
* Kind of the the types that are relative to the stack of an input
* stack map frame. The value of such types is a position relatively
* to the top of this stack.
*/
private final static int STACK = 0x3000000;
/**
* The TOP type. This is a BASE type.
*/
final static int TOP = BASE | 0;
/**
* The BOOLEAN type. This is a BASE type mainly used for array types.
*/
final static int BOOLEAN = BASE | 9;
/**
* The BYTE type. This is a BASE type mainly used for array types.
*/
final static int BYTE = BASE | 10;
7.12. FRAME.JAVA 273
/**
* The CHAR type. This is a BASE type mainly used for array types.
*/
final static int CHAR = BASE | 11;
/**
* The SHORT type. This is a BASE type mainly used for array types.
*/
final static int SHORT = BASE | 12;
/**
* The INTEGER type. This is a BASE type.
*/
final static int INTEGER = BASE | 1;
/**
* The FLOAT type. This is a BASE type.
*/
final static int FLOAT = BASE | 2;
/**
* The DOUBLE type. This is a BASE type.
*/
final static int DOUBLE = BASE | 3;
/**
* The LONG type. This is a BASE type.
*/
final static int LONG = BASE | 4;
/**
* The NULL type. This is a BASE type.
*/
final static int NULL = BASE | 5;
/**
* The UNINITIALIZED_THIS type. This is a BASE type.
*/
final static int UNINITIALIZED_THIS = BASE | 6;
/**
* The stack size variation corresponding to each JVM instruction.
* This stack variation is equal to the size of the values produced
* by an instruction, minus the size of the values consumed by this
* instruction.
*/
final static int[] SIZE;
/**
* Computes the stack size variation corresponding to each JVM
274 CHAPTER 7. JVM/CLOJURE/ASM/
* instruction.
*/
static
{
int i;
int[] b = new int[202];
String s =
"EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD"
+ "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD"
+ "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED"
+ "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE";
for(i = 0; i < b.length; ++i)
{
b[i] = s.charAt(i) - E;
}
SIZE = b;
// NA, //ILOAD_1, // -
// NA, //ILOAD_2, // -
// NA, //ILOAD_3, // -
// NA, //LLOAD_0, // -
// NA, //LLOAD_1, // -
// NA, //LLOAD_2, // -
// NA, //LLOAD_3, // -
// NA, //FLOAD_0, // -
// NA, //FLOAD_1, // -
// NA, //FLOAD_2, // -
// NA, //FLOAD_3, // -
// NA, //DLOAD_0, // -
// NA, //DLOAD_1, // -
// NA, //DLOAD_2, // -
// NA, //DLOAD_3, // -
// NA, //ALOAD_0, // -
// NA, //ALOAD_1, // -
// NA, //ALOAD_2, // -
// NA, //ALOAD_3, // -
// -1, //IALOAD, // visitInsn
// 0, //LALOAD, // -
// -1, //FALOAD, // -
// 0, //DALOAD, // -
// -1, //AALOAD, // -
// -1, //BALOAD, // -
// -1, //CALOAD, // -
// -1, //SALOAD, // -
// -1, //ISTORE, // visitVarInsn
// -2, //LSTORE, // -
// -1, //FSTORE, // -
// -2, //DSTORE, // -
// -1, //ASTORE, // -
// NA, //ISTORE_0, // -
// NA, //ISTORE_1, // -
// NA, //ISTORE_2, // -
// NA, //ISTORE_3, // -
// NA, //LSTORE_0, // -
// NA, //LSTORE_1, // -
// NA, //LSTORE_2, // -
// NA, //LSTORE_3, // -
// NA, //FSTORE_0, // -
// NA, //FSTORE_1, // -
// NA, //FSTORE_2, // -
// NA, //FSTORE_3, // -
// NA, //DSTORE_0, // -
// NA, //DSTORE_1, // -
// NA, //DSTORE_2, // -
// NA, //DSTORE_3, // -
// NA, //ASTORE_0, // -
// NA, //ASTORE_1, // -
276 CHAPTER 7. JVM/CLOJURE/ASM/
// NA, //ASTORE_2, // -
// NA, //ASTORE_3, // -
// -3, //IASTORE, // visitInsn
// -4, //LASTORE, // -
// -3, //FASTORE, // -
// -4, //DASTORE, // -
// -3, //AASTORE, // -
// -3, //BASTORE, // -
// -3, //CASTORE, // -
// -3, //SASTORE, // -
// -1, //POP, // -
// -2, //POP2, // -
// 1, //DUP, // -
// 1, //DUP_X1, // -
// 1, //DUP_X2, // -
// 2, //DUP2, // -
// 2, //DUP2_X1, // -
// 2, //DUP2_X2, // -
// 0, //SWAP, // -
// -1, //IADD, // -
// -2, //LADD, // -
// -1, //FADD, // -
// -2, //DADD, // -
// -1, //ISUB, // -
// -2, //LSUB, // -
// -1, //FSUB, // -
// -2, //DSUB, // -
// -1, //IMUL, // -
// -2, //LMUL, // -
// -1, //FMUL, // -
// -2, //DMUL, // -
// -1, //IDIV, // -
// -2, //LDIV, // -
// -1, //FDIV, // -
// -2, //DDIV, // -
// -1, //IREM, // -
// -2, //LREM, // -
// -1, //FREM, // -
// -2, //DREM, // -
// 0, //INEG, // -
// 0, //LNEG, // -
// 0, //FNEG, // -
// 0, //DNEG, // -
// -1, //ISHL, // -
// -1, //LSHL, // -
// -1, //ISHR, // -
// -1, //LSHR, // -
// -1, //IUSHR, // -
// -1, //LUSHR, // -
// -1, //IAND, // -
7.12. FRAME.JAVA 277
// -2, //LAND, // -
// -1, //IOR, // -
// -2, //LOR, // -
// -1, //IXOR, // -
// -2, //LXOR, // -
// 0, //IINC, // visitIincInsn
// 1, //I2L, // visitInsn
// 0, //I2F, // -
// 1, //I2D, // -
// -1, //L2I, // -
// -1, //L2F, // -
// 0, //L2D, // -
// 0, //F2I, // -
// 1, //F2L, // -
// 1, //F2D, // -
// -1, //D2I, // -
// 0, //D2L, // -
// -1, //D2F, // -
// 0, //I2B, // -
// 0, //I2C, // -
// 0, //I2S, // -
// -3, //LCMP, // -
// -1, //FCMPL, // -
// -1, //FCMPG, // -
// -3, //DCMPL, // -
// -3, //DCMPG, // -
// -1, //IFEQ, // visitJumpInsn
// -1, //IFNE, // -
// -1, //IFLT, // -
// -1, //IFGE, // -
// -1, //IFGT, // -
// -1, //IFLE, // -
// -2, //IF_ICMPEQ, // -
// -2, //IF_ICMPNE, // -
// -2, //IF_ICMPLT, // -
// -2, //IF_ICMPGE, // -
// -2, //IF_ICMPGT, // -
// -2, //IF_ICMPLE, // -
// -2, //IF_ACMPEQ, // -
// -2, //IF_ACMPNE, // -
// 0, //GOTO, // -
// 1, //JSR, // -
// 0, //RET, // visitVarInsn
// -1, //TABLESWITCH, // visiTableSwitchInsn
// -1, //LOOKUPSWITCH, // visitLookupSwitch
// -1, //IRETURN, // visitInsn
// -2, //LRETURN, // -
// -1, //FRETURN, // -
// -2, //DRETURN, // -
// -1, //ARETURN, // -
278 CHAPTER 7. JVM/CLOJURE/ASM/
// 0, //RETURN, // -
// NA, //GETSTATIC, // visitFieldInsn
// NA, //PUTSTATIC, // -
// NA, //GETFIELD, // -
// NA, //PUTFIELD, // -
// NA, //INVOKEVIRTUAL, // visitMethodInsn
// NA, //INVOKESPECIAL, // -
// NA, //INVOKESTATIC, // -
// NA, //INVOKEINTERFACE, // -
// NA, //UNUSED, // NOT VISITED
// 1, //NEW, // visitTypeInsn
// 0, //NEWARRAY, // visitIntInsn
// 0, //ANEWARRAY, // visitTypeInsn
// 0, //ARRAYLENGTH, // visitInsn
// NA, //ATHROW, // -
// 0, //CHECKCAST, // visitTypeInsn
// 0, //INSTANCEOF, // -
// -1, //MONITORENTER, // visitInsn
// -1, //MONITOREXIT, // -
// NA, //WIDE, // NOT VISITED
// NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn
// -1, //IFNULL, // visitJumpInsn
// -1, //IFNONNULL, // -
// NA, //GOTO_W, // -
// NA, //JSR_W, // -
// };
// for (i = 0; i < b.length; ++i) {
// System.err.print((char)(E + b[i]));
// }
// System.err.println();
}
/**
* The label (i.e. basic block) to which these input and output stack map
* frames correspond.
*/
Label owner;
/**
* The input stack map frame locals.
*/
int[] inputLocals;
/**
* The input stack map frame stack.
*/
int[] inputStack;
/**
* The output stack map frame locals.
7.12. FRAME.JAVA 279
*/
private int[] outputLocals;
/**
* The output stack map frame stack.
*/
private int[] outputStack;
/**
* Relative size of the output stack. The exact semantics of this field
* depends on the algorithm that is used.
* <p/>
* When only the maximum stack size is computed, this field is the size
* of the output stack relatively to the top of the input stack.
* <p/>
* When the stack map frames are completely computed, this field is the
* actual number of types in {@link #outputStack}.
*/
private int outputStackTop;
/**
* Number of types that are initialized in the basic block.
*
* @see #initializations
*/
private int initializationCount;
/**
* The types that are initialized in the basic block. A constructor
* invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must
* replace <i>every occurence</i> of this type in the local variables
* and in the operand stack. This cannot be done during the first
* phase of the algorithm since, during this phase, the local variables
* and the operand stack are not completely computed. It is therefore
* necessary to store the types on which constructors are invoked in
* the basic block, in order to do this replacement during the second
* phase of the algorithm, where the frames are fully computed. Note
* that this array can contain types that are relative to input locals
* or to the input stack (see below for the description of the
* algorithm).
*/
private int[] initializations;
/**
* Returns the output frame local variable type at the given index.
*
* @param local the index of the local that must be returned.
* @return the output frame local variable type at the given index.
*/
private int get(final int local){
280 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Sets the output frame local variable type at the given index.
*
* @param local the index of the local that must be set.
* @param type the value of the local that must be set.
*/
private void set(final int local, final int type){
// creates and/or resizes the output local variables
// array if necessary
if(outputLocals == null)
{
outputLocals = new int[10];
}
int n = outputLocals.length;
if(local >= n)
{
int[] t = new int[Math.max(local + 1, 2 * n)];
System.arraycopy(outputLocals, 0, t, 0, n);
outputLocals = t;
}
// sets the local variable
outputLocals[local] = type;
}
/**
* Pushes a new type onto the output frame stack.
*
* @param type the type that must be pushed.
*/
private void push(final int type){
7.12. FRAME.JAVA 281
/**
* Pushes a new type onto the output frame stack.
*
* @param cw the ClassWriter to which this label belongs.
* @param desc the descriptor of the type to be pushed. Can also be a
* method descriptor (in this case this method pushes its
* return type onto the output frame stack).
*/
private void push(final ClassWriter cw, final String desc){
int type = type(cw, desc);
if(type != 0)
{
push(type);
if(type == LONG || type == DOUBLE)
{
push(TOP);
}
}
}
/**
* Returns the int encoding of the given type.
*
* @param cw the ClassWriter to which this label belongs.
* @param desc a type descriptor.
* @return the int encoding of the given type.
*/
private int type(final ClassWriter cw, final String desc){
282 CHAPTER 7. JVM/CLOJURE/ASM/
String t;
int index = desc.charAt(0) == ( ? desc.indexOf()) + 1 : 0;
switch(desc.charAt(index))
{
caseV:
return 0;
caseZ:
caseC:
caseB:
caseS:
caseI:
return INTEGER;
caseF:
return FLOAT;
caseJ:
return LONG;
caseD:
return DOUBLE;
caseL:
// stores the internal name, not the descriptor!
t = desc.substring(index + 1, desc.length() - 1);
return OBJECT | cw.addType(t);
// case [:
default:
// extracts the dimensions and the element type
int data;
int dims = index + 1;
while(desc.charAt(dims) == [)
{
++dims;
}
switch(desc.charAt(dims))
{
caseZ:
data = BOOLEAN;
break;
caseC:
data = CHAR;
break;
caseB:
data = BYTE;
break;
caseS:
data = SHORT;
break;
caseI:
data = INTEGER;
break;
caseF:
data = FLOAT;
7.12. FRAME.JAVA 283
break;
caseJ:
data = LONG;
break;
caseD:
data = DOUBLE;
break;
// case L:
default:
// stores the internal name, not the descriptor
t = desc.substring(dims + 1, desc.length() - 1);
data = OBJECT | cw.addType(t);
}
return (dims - index) << 28 | data;
}
}
/**
* Pops a type from the output frame stack and returns its value.
*
* @return the type that has been popped from the output frame stack.
*/
private int pop(){
if(outputStackTop > 0)
{
return outputStack[--outputStackTop];
}
else
{
// if the output frame stack is empty, pops from the input stack
return STACK | -(--owner.inputStackTop);
}
}
/**
* Pops the given number of types from the output frame stack.
*
* @param elements the number of types that must be popped.
*/
private void pop(final int elements){
if(outputStackTop >= elements)
{
outputStackTop -= elements;
}
else
{
// if the number of elements to be popped is greater than the
// number of elements in the output stack, clear it, and pops
// the remaining elements from the input stack.
owner.inputStackTop -= elements - outputStackTop;
284 CHAPTER 7. JVM/CLOJURE/ASM/
outputStackTop = 0;
}
}
/**
* Pops a type from the output frame stack.
*
* @param desc the descriptor of the type to be popped. Can also be a
* method descriptor (in this case this method pops the
* types corresponding to the method arguments).
*/
private void pop(final String desc){
char c = desc.charAt(0);
if(c == ()
{
pop((MethodWriter.getArgumentsAndReturnSizes(desc) >> 2) - 1);
}
else if(c == J || c == D)
{
pop(2);
}
else
{
pop(1);
}
}
/**
* Adds a new type to the list of types on which a constructor is
* invoked in the basic block.
*
* @param var a type on a which a constructor is invoked.
*/
private void init(final int var){
// creates and/or resizes the initializations array if necessary
if(initializations == null)
{
initializations = new int[2];
}
int n = initializations.length;
if(initializationCount >= n)
{
int[] t = new int[Math.max(initializationCount + 1, 2 * n)];
System.arraycopy(initializations, 0, t, 0, n);
initializations = t;
}
// stores the type to be initialized
initializations[initializationCount++] = var;
}
7.12. FRAME.JAVA 285
/**
* Replaces the given type with the appropriate type if it is one of
* the types on which a constructor is invoked in the basic block.
*
* @param cw the ClassWriter to which this label belongs.
* @param t a type
* @return t or, if t is one of the types on which a constructor is
* invoked in the basic block, the type corresponding to this
* constructor.
*/
private int init(final ClassWriter cw, final int t){
int s;
if(t == UNINITIALIZED_THIS)
{
s = OBJECT | cw.addType(cw.thisName);
}
else if((t & (DIM | BASE_KIND)) == UNINITIALIZED)
{
String type = cw.typeTable[t & BASE_VALUE].strVal1;
s = OBJECT | cw.addType(type);
}
else
{
return t;
}
for(int j = 0; j < initializationCount; ++j)
{
int u = initializations[j];
int dim = u & DIM;
int kind = u & KIND;
if(kind == LOCAL)
{
u = dim + inputLocals[u & VALUE];
}
else if(kind == STACK)
{
u = dim + inputStack[inputStack.length - (u & VALUE)];
}
if(t == u)
{
return s;
}
}
return t;
}
/**
* Initializes the input frame of the first basic block from the method
* descriptor.
*
286 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Simulates the action of the given instruction on the output stack
* frame.
*
* @param opcode the opcode of the instruction.
* @param arg the operand of the instruction, if any.
* @param cw the class writer to which this label belongs.
* @param item the operand of the instructions, if any.
*/
void execute(
7.12. FRAME.JAVA 287
break;
case Opcodes.LDC:
switch(item.type)
{
case ClassWriter.INT:
push(INTEGER);
break;
case ClassWriter.LONG:
push(LONG);
push(TOP);
break;
case ClassWriter.FLOAT:
push(FLOAT);
break;
case ClassWriter.DOUBLE:
push(DOUBLE);
push(TOP);
break;
case ClassWriter.CLASS:
push(OBJECT | cw.addType("java/lang/Class"));
break;
// case ClassWriter.STR:
default:
push(OBJECT | cw.addType("java/lang/String"));
}
break;
case Opcodes.ALOAD:
push(get(arg));
break;
case Opcodes.IALOAD:
case Opcodes.BALOAD:
case Opcodes.CALOAD:
case Opcodes.SALOAD:
pop(2);
push(INTEGER);
break;
case Opcodes.LALOAD:
case Opcodes.D2L:
pop(2);
push(LONG);
push(TOP);
break;
case Opcodes.FALOAD:
pop(2);
push(FLOAT);
break;
case Opcodes.DALOAD:
case Opcodes.L2D:
pop(2);
push(DOUBLE);
7.12. FRAME.JAVA 289
push(TOP);
break;
case Opcodes.AALOAD:
pop(1);
t1 = pop();
push(ELEMENT_OF + t1);
break;
case Opcodes.ISTORE:
case Opcodes.FSTORE:
case Opcodes.ASTORE:
t1 = pop();
set(arg, t1);
if(arg > 0)
{
t2 = get(arg - 1);
// if t2 is of kind STACK or LOCAL we cannot know
// its size!
if(t2 == LONG || t2 == DOUBLE)
{
set(arg - 1, TOP);
}
}
break;
case Opcodes.LSTORE:
case Opcodes.DSTORE:
pop(1);
t1 = pop();
set(arg, t1);
set(arg + 1, TOP);
if(arg > 0)
{
t2 = get(arg - 1);
// if t2 is of kind STACK or LOCAL we cannot know
// its size!
if(t2 == LONG || t2 == DOUBLE)
{
set(arg - 1, TOP);
}
}
break;
case Opcodes.IASTORE:
case Opcodes.BASTORE:
case Opcodes.CASTORE:
case Opcodes.SASTORE:
case Opcodes.FASTORE:
case Opcodes.AASTORE:
pop(3);
break;
case Opcodes.LASTORE:
case Opcodes.DASTORE:
290 CHAPTER 7. JVM/CLOJURE/ASM/
pop(4);
break;
case Opcodes.POP:
case Opcodes.IFEQ:
case Opcodes.IFNE:
case Opcodes.IFLT:
case Opcodes.IFGE:
case Opcodes.IFGT:
case Opcodes.IFLE:
case Opcodes.IRETURN:
case Opcodes.FRETURN:
case Opcodes.ARETURN:
case Opcodes.TABLESWITCH:
case Opcodes.LOOKUPSWITCH:
case Opcodes.ATHROW:
case Opcodes.MONITORENTER:
case Opcodes.MONITOREXIT:
case Opcodes.IFNULL:
case Opcodes.IFNONNULL:
pop(1);
break;
case Opcodes.POP2:
case Opcodes.IF_ICMPEQ:
case Opcodes.IF_ICMPNE:
case Opcodes.IF_ICMPLT:
case Opcodes.IF_ICMPGE:
case Opcodes.IF_ICMPGT:
case Opcodes.IF_ICMPLE:
case Opcodes.IF_ACMPEQ:
case Opcodes.IF_ACMPNE:
case Opcodes.LRETURN:
case Opcodes.DRETURN:
pop(2);
break;
case Opcodes.DUP:
t1 = pop();
push(t1);
push(t1);
break;
case Opcodes.DUP_X1:
t1 = pop();
t2 = pop();
push(t1);
push(t2);
push(t1);
break;
case Opcodes.DUP_X2:
t1 = pop();
t2 = pop();
t3 = pop();
7.12. FRAME.JAVA 291
push(t1);
push(t3);
push(t2);
push(t1);
break;
case Opcodes.DUP2:
t1 = pop();
t2 = pop();
push(t2);
push(t1);
push(t2);
push(t1);
break;
case Opcodes.DUP2_X1:
t1 = pop();
t2 = pop();
t3 = pop();
push(t2);
push(t1);
push(t3);
push(t2);
push(t1);
break;
case Opcodes.DUP2_X2:
t1 = pop();
t2 = pop();
t3 = pop();
t4 = pop();
push(t2);
push(t1);
push(t4);
push(t3);
push(t2);
push(t1);
break;
case Opcodes.SWAP:
t1 = pop();
t2 = pop();
push(t1);
push(t2);
break;
case Opcodes.IADD:
case Opcodes.ISUB:
case Opcodes.IMUL:
case Opcodes.IDIV:
case Opcodes.IREM:
case Opcodes.IAND:
case Opcodes.IOR:
case Opcodes.IXOR:
case Opcodes.ISHL:
292 CHAPTER 7. JVM/CLOJURE/ASM/
case Opcodes.ISHR:
case Opcodes.IUSHR:
case Opcodes.L2I:
case Opcodes.D2I:
case Opcodes.FCMPL:
case Opcodes.FCMPG:
pop(2);
push(INTEGER);
break;
case Opcodes.LADD:
case Opcodes.LSUB:
case Opcodes.LMUL:
case Opcodes.LDIV:
case Opcodes.LREM:
case Opcodes.LAND:
case Opcodes.LOR:
case Opcodes.LXOR:
pop(4);
push(LONG);
push(TOP);
break;
case Opcodes.FADD:
case Opcodes.FSUB:
case Opcodes.FMUL:
case Opcodes.FDIV:
case Opcodes.FREM:
case Opcodes.L2F:
case Opcodes.D2F:
pop(2);
push(FLOAT);
break;
case Opcodes.DADD:
case Opcodes.DSUB:
case Opcodes.DMUL:
case Opcodes.DDIV:
case Opcodes.DREM:
pop(4);
push(DOUBLE);
push(TOP);
break;
case Opcodes.LSHL:
case Opcodes.LSHR:
case Opcodes.LUSHR:
pop(3);
push(LONG);
push(TOP);
break;
case Opcodes.IINC:
set(arg, INTEGER);
break;
7.12. FRAME.JAVA 293
case Opcodes.I2L:
case Opcodes.F2L:
pop(1);
push(LONG);
push(TOP);
break;
case Opcodes.I2F:
pop(1);
push(FLOAT);
break;
case Opcodes.I2D:
case Opcodes.F2D:
pop(1);
push(DOUBLE);
push(TOP);
break;
case Opcodes.F2I:
case Opcodes.ARRAYLENGTH:
case Opcodes.INSTANCEOF:
pop(1);
push(INTEGER);
break;
case Opcodes.LCMP:
case Opcodes.DCMPL:
case Opcodes.DCMPG:
pop(4);
push(INTEGER);
break;
case Opcodes.JSR:
case Opcodes.RET:
throw new RuntimeException(
"JSR/RET are not supported with computeFrames option");
case Opcodes.GETSTATIC:
push(cw, item.strVal3);
break;
case Opcodes.PUTSTATIC:
pop(item.strVal3);
break;
case Opcodes.GETFIELD:
pop(1);
push(cw, item.strVal3);
break;
case Opcodes.PUTFIELD:
pop(item.strVal3);
pop();
break;
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEINTERFACE:
294 CHAPTER 7. JVM/CLOJURE/ASM/
pop(item.strVal3);
if(opcode != Opcodes.INVOKESTATIC)
{
t1 = pop();
if(opcode == Opcodes.INVOKESPECIAL
&& item.strVal2.charAt(0) == <)
{
init(t1);
}
}
push(cw, item.strVal3);
break;
case Opcodes.NEW:
push(UNINITIALIZED |
cw.addUninitializedType(item.strVal1, arg));
break;
case Opcodes.NEWARRAY:
pop();
switch(arg)
{
case Opcodes.T_BOOLEAN:
push(ARRAY_OF | BOOLEAN);
break;
case Opcodes.T_CHAR:
push(ARRAY_OF | CHAR);
break;
case Opcodes.T_BYTE:
push(ARRAY_OF | BYTE);
break;
case Opcodes.T_SHORT:
push(ARRAY_OF | SHORT);
break;
case Opcodes.T_INT:
push(ARRAY_OF | INTEGER);
break;
case Opcodes.T_FLOAT:
push(ARRAY_OF | FLOAT);
break;
case Opcodes.T_DOUBLE:
push(ARRAY_OF | DOUBLE);
break;
// case Opcodes.T_LONG:
default:
push(ARRAY_OF | LONG);
break;
}
break;
case Opcodes.ANEWARRAY:
String s = item.strVal1;
pop();
7.12. FRAME.JAVA 295
if(s.charAt(0) == [)
{
push(cw, "[" + s);
}
else
{
push(ARRAY_OF | OBJECT | cw.addType(s));
}
break;
case Opcodes.CHECKCAST:
s = item.strVal1;
pop();
if(s.charAt(0) == [)
{
push(cw, s);
}
else
{
push(OBJECT | cw.addType(s));
}
break;
// case Opcodes.MULTIANEWARRAY:
default:
pop(arg);
push(cw, item.strVal1);
break;
}
}
/**
* Merges the input frame of the given basic block with the input and
* output frames of this basic block. Returns <tt>true</tt> if the
* input frame of the given label has been changed by this operation.
*
* @param cw the ClassWriter to which this label belongs.
* @param frame the basic block whose input frame must be updated.
* @param edge the kind of the {@link Edge} between this label and
* label. See {@link Edge#info}.
* @return <tt>true</tt> if the input frame of the given label has been
* changed by this operation.
*/
boolean merge(final ClassWriter cw, final Frame frame, final int edge){
boolean changed = false;
int i, s, dim, kind, t;
changed = true;
}
if(edge > 0)
{
for(i = 0; i < nLocal; ++i)
{
t = inputLocals[i];
changed |= merge(cw, t, frame.inputLocals, i);
}
if(frame.inputStack == null)
{
7.12. FRAME.JAVA 297
/**
298 CHAPTER 7. JVM/CLOJURE/ASM/
* Merges the type at the given index in the given type array with
* the given type. Returns <tt>true</tt> if the type array has been
* modified by this operation.
*
* @param cw the ClassWriter to which this label belongs.
* @param t the type with which the type array element must be
* merged.
* @param types an array of types.
* @param index the index of the type that must be merged in types.
* @return <tt>true</tt> if the type array has been modified by this
* operation.
*/
private boolean merge(
final ClassWriter cw,
int t,
final int[] types,
final int index){
int u = types[index];
if(u == t)
{
// if the types are equal, merge(u,t)=u, so there is no change
return false;
}
if((t & ~DIM) == NULL)
{
if(u == NULL)
{
return false;
}
t = NULL;
}
if(u == 0)
{
// if types[index] has never been assigned, merge(u,t)=t
types[index] = t;
return true;
}
int v;
if((u & BASE_KIND) == OBJECT || (u & DIM) != 0)
{
// if u is a reference type of any dimension
if(t == NULL)
{
// if t is the NULL type, merge(u,t)=u, so there is no change
return false;
}
else if((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND)))
{
if((u & BASE_KIND) == OBJECT)
{
7.12. FRAME.JAVA 299
-
300 CHAPTER 7. JVM/CLOJURE/ASM/
7.13 Handler.java
Handler.java
/**
* Information about an exception handler block.
*
* @author Eric Bruneton
*/
class Handler{
/**
* Beginning of the exception handlers scope (inclusive).
*/
Label start;
/**
* End of the exception handlers scope (exclusive).
*/
Label end;
/**
* Beginning of the exception handlers code.
*/
Label handler;
/**
* Internal name of the type of exceptions handled by this handler, or
* <tt>null</tt> to catch any exceptions.
*/
String desc;
/**
* Constant pool index of the internal name of the type of exceptions
* handled by this handler, or 0 to catch any exceptions.
*/
int type;
/**
* Next exception handler block info.
*/
Handler next;
}
-
7.14. ITEM.JAVA 301
7.14 Item.java
Item.java
/**
* A constant pool item. Constant pool items can be created with the
* newXXX methods in the {@link ClassWriter} class.
*
* @author Eric Bruneton
*/
final class Item{
/**
* Index of this item in the constant pool.
*/
int index;
/**
* Type of this constant pool item. A single class is used to represent
* all constant pool item types, in order to minimize the bytecode size
* of this package. The value of this field is one of
* {@link ClassWriter#INT},
* {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT},
* {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8},
* {@link ClassWriter#STR}, {@link ClassWriter#CLASS},
* {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD},
* {@link ClassWriter#METH}, {@link ClassWriter#IMETH}.
* <p/>
* Special Item types are used for Items that are stored in the
* ClassWriter {@link ClassWriter#typeTable}, instead of the constant
* pool, in order to avoid clashes with normal constant pool items in
* the ClassWriter constant pools hash table. These special item types
* are {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT}
* and {@link ClassWriter#TYPE_MERGED}.
*/
int type;
/**
* Value of this item, for an integer item.
*/
int intVal;
/**
* Value of this item, for a long item.
*/
302 CHAPTER 7. JVM/CLOJURE/ASM/
long longVal;
/**
* First part of the value of this item, for items that do not hold a
* primitive value.
*/
String strVal1;
/**
* Second part of the value of this item, for items that do not hold a
* primitive value.
*/
String strVal2;
/**
* Third part of the value of this item, for items that do not hold a
* primitive value.
*/
String strVal3;
/**
* The hash code value of this constant pool item.
*/
int hashCode;
/**
* Link to another constant pool item, used for collision lists in the
* constant pools hash table.
*/
Item next;
/**
* Constructs an uninitialized {@link Item}.
*/
Item(){
}
/**
* Constructs an uninitialized {@link Item} for constant pool element at
* given position.
*
* @param index index of the item to be constructed.
*/
Item(final int index){
this.index = index;
}
/**
* Constructs a copy of the given item.
*
7.14. ITEM.JAVA 303
/**
* Sets this item to an integer item.
*
* @param intVal the value of this item.
*/
void set(final int intVal){
this.type = ClassWriter.INT;
this.intVal = intVal;
this.hashCode = 0x7FFFFFFF & (type + intVal);
}
/**
* Sets this item to a long item.
*
* @param longVal the value of this item.
*/
void set(final long longVal){
this.type = ClassWriter.LONG;
this.longVal = longVal;
this.hashCode = 0x7FFFFFFF & (type + (int) longVal);
}
/**
* Sets this item to a float item.
*
* @param floatVal the value of this item.
*/
void set(final float floatVal){
this.type = ClassWriter.FLOAT;
this.intVal = Float.floatToRawIntBits(floatVal);
this.hashCode = 0x7FFFFFFF & (type + (int) floatVal);
}
/**
* Sets this item to a double item.
304 CHAPTER 7. JVM/CLOJURE/ASM/
*
* @param doubleVal the value of this item.
*/
void set(final double doubleVal){
this.type = ClassWriter.DOUBLE;
this.longVal = Double.doubleToRawLongBits(doubleVal);
this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal);
}
/**
* Sets this item to an item that do not hold a primitive value.
*
* @param type the type of this item.
* @param strVal1 first part of the value of this item.
* @param strVal2 second part of the value of this item.
* @param strVal3 third part of the value of this item.
*/
void set(
final int type,
final String strVal1,
final String strVal2,
final String strVal3){
this.type = type;
this.strVal1 = strVal1;
this.strVal2 = strVal2;
this.strVal3 = strVal3;
switch(type)
{
case ClassWriter.UTF8:
case ClassWriter.STR:
case ClassWriter.CLASS:
case ClassWriter.TYPE_NORMAL:
hashCode = 0x7FFFFFFF & (type + strVal1.hashCode());
return;
case ClassWriter.NAME_TYPE:
hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
* strVal2.hashCode());
return;
// ClassWriter.FIELD:
// ClassWriter.METH:
// ClassWriter.IMETH:
default:
hashCode = 0x7FFFFFFF &
(type + strVal1.hashCode()
* strVal2.hashCode() * strVal3.hashCode());
}
}
/**
* Indicates if the given item is equal to this one.
7.15. LABEL.JAVA 305
*
* @param i the item to be compared to this one.
* @return <tt>true</tt> if the given item if equal to this one,
* <tt>false</tt> otherwise.
*/
boolean isEqualTo(final Item i){
if(i.type == type)
{
switch(type)
{
case ClassWriter.INT:
case ClassWriter.FLOAT:
return i.intVal == intVal;
case ClassWriter.TYPE_MERGED:
case ClassWriter.LONG:
case ClassWriter.DOUBLE:
return i.longVal == longVal;
case ClassWriter.UTF8:
case ClassWriter.STR:
case ClassWriter.CLASS:
case ClassWriter.TYPE_NORMAL:
return i.strVal1.equals(strVal1);
case ClassWriter.TYPE_UNINIT:
return i.intVal == intVal && i.strVal1.equals(strVal1);
case ClassWriter.NAME_TYPE:
return i.strVal1.equals(strVal1)
&& i.strVal2.equals(strVal2);
// ClassWriter.FIELD:
// ClassWriter.METH:
// ClassWriter.IMETH:
default:
return i.strVal1.equals(strVal1)
&& i.strVal2.equals(strVal2)
&& i.strVal3.equals(strVal3);
}
}
return false;
}
}
7.15 Label.java
Label.java
package clojure.asm;
/**
* A label represents a position in the bytecode of a method. Labels
* are used for jump, goto, and switch instructions, and for try
* catch blocks.
*
* @author Eric Bruneton
*/
public class Label{
/**
* Indicates if this label is only used for debug attributes. Such a
* label is not the start of a basic block, the target of a jump
* instruction, or an exception handler. It can be safely ignored
* in control flow graph analysis algorithms (for optimization
* purposes).
*/
final static int DEBUG = 1;
/**
* Indicates if the position of this label is known.
*/
final static int RESOLVED = 2;
/**
* Indicates if this label has been updated, after instruction resizing.
*/
final static int RESIZED = 4;
/**
* Indicates if this basic block has been pushed in the basic block
* stack. See {@link MethodWriter#visitMaxs visitMaxs}.
*/
final static int PUSHED = 8;
/**
* Indicates if this label is the target of a jump instruction, or
* the start of an exception handler.
*/
final static int TARGET = 16;
/**
* Indicates if a stack map frame must be stored for this label.
*/
final static int STORE = 32;
/**
* Indicates if this label corresponds to a reachable basic block.
*/
7.15. LABEL.JAVA 307
/**
* Indicates if this basic block ends with a JSR instruction.
*/
final static int JSR = 128;
/**
* Indicates if this basic block ends with a RET instruction.
*/
final static int RET = 256;
/**
* Field used to associate user information to a label.
*/
public Object info;
/**
* Flags that indicate the status of this label.
*
* @see #DEBUG
* @see #RESOLVED
* @see #RESIZED
* @see #PUSHED
* @see #TARGET
* @see #STORE
* @see #REACHABLE
* @see #JSR
* @see #RET
*/
int status;
/**
* The line number corresponding to this label, if known.
*/
int line;
/**
* The position of this label in the code, if known.
*/
int position;
/**
* Number of forward references to this label, times two.
*/
private int referenceCount;
/**
* Informations about forward references. Each forward reference is
* described by two consecutive integers in this array: the first one
308 CHAPTER 7. JVM/CLOJURE/ASM/
// -------------------------------------------------------------------
/*
* Fields for the control flow and data flow graph analysis algorithms
* (used to compute the maximum stack size or the stack map frames).
* A control flow graph contains one node per "basic block", and one
* edge per "jump" from one basic block to another. Each node (i.e.,
* each basic block) is represented by the Label object that
* corresponds to the first instruction of this basic block. Each node
* also stores the list of its successors in the graph, as a linked
* list of Edge objects.
*
* The control flow analysis algorithms used to compute the maximum
* stack size or the stack map frames are similar and use two steps.
* The first step, during the visit of each instruction, builds
* information about the state of the local variables and the operand
* stack at the end of each basic block, called the "output frame",
* <i>relatively</i> to the frame state at the beginning of the basic
* block, which is called the "input frame", and which is <i>unknown</i>
* during this step. The second step, in
* {@link MethodWriter#visitMaxs}, is a fix point algorithm that
* icomputes information about the input frame of each basic block,
* from the nput state of the first basic block (known from the
* method signature), and by the using the previously computed
* relative output frames.
*
* The algorithm used to compute the maximum stack size only computes
* the relative output and absolute input stack heights, while the
* algorithm used to compute stack map frames computes relative
* output frames and absolute input frames.
*/
/**
* Start of the output stack relatively to the input stack. The exact
* semantics of this field depends on the algorithm that is used.
* <p/>
* When only the maximum stack size is computed, this field is the
* number of elements in the input stack.
* <p/>
* When the stack map frames are completely computed, this field is
* the offset of the first output stack element relatively to the top
7.15. LABEL.JAVA 309
/**
* Maximum height reached by the output stack, relatively to the top
* of the input stack. This maximum is always positive or null.
*/
int outputStackMax;
/**
* Information about the input and output stack map frames of this
* basic block. This field is only used when
* {@link ClassWriter#COMPUTE_FRAMES} option is used.
*/
Frame frame;
/**
* The successor of this label, in the order they are visited. This
* linked list does not include labels used for debug info only. If
* {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition,
* it does not contain successive labels that denote the same
* bytecode position (in this case only the first label appears in
* this list).
*/
Label successor;
/**
* The successors of this node in the control flow graph. These
* successors are stored in a linked list of {@link Edge Edge}
* objects, linked to each other by their {@link Edge#next} field.
*/
Edge successors;
/**
* The next basic block in the basic block stack. This stack is used
* in the main loop of the fix point algorithm used in the second step
* of the control flow analysis algorithms.
*
* @see MethodWriter#visitMaxs
*/
Label next;
// -------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------
310 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Constructs a new label.
*/
public Label(){
}
/**
* Constructs a new label.
*
* @param debug if this label is only used for debug attributes.
*/
Label(final boolean debug){
this.status = debug ? DEBUG : 0;
}
// -------------------------------------------------------------------
// Methods to compute offsets and to manage forward references
// -------------------------------------------------------------------
/**
* Returns the offset corresponding to this label. This offset is
* computed from the start of the methods bytecode. <i>This method
* is intended for {@link Attribute} sub classes, and is normally
* not needed by class generators or adapters.</i>
*
* @return the offset corresponding to this label.
* @throws IllegalStateException if this label is not resolved yet.
*/
public int getOffset(){
if((status & RESOLVED) == 0)
{
throw new IllegalStateException(
"Label offset position has not been resolved yet");
}
return position;
}
/**
* Puts a reference to this label in the bytecode of a method. If the
* position of the label is known, the offset is computed and written
* directly. Otherwise, a null offset is written and a new forward
* reference is declared for this label.
*
* @param owner the code writer that calls this method.
* @param out the bytecode of the method.
* @param source the position of first byte of the bytecode
* instruction that contains this label.
* @param wideOffset <tt>true</tt> if the reference must be stored in 4
* bytes, or <tt>false</tt> if it must be stored with 2
7.15. LABEL.JAVA 311
* bytes.
* @throws IllegalArgumentException if this label has not been created by
* the given code writer.
*/
void put(
final MethodWriter owner,
final ByteVector out,
final int source,
final boolean wideOffset){
if((status & RESOLVED) != 0)
{
if(wideOffset)
{
out.putInt(position - source);
}
else
{
out.putShort(position - source);
}
}
else
{
if(wideOffset)
{
addReference(-1 - source, out.length);
out.putInt(-1);
}
else
{
addReference(source, out.length);
out.putShort(-1);
}
}
}
/**
* Adds a forward reference to this label. This method must be called
* only for a true forward reference, i.e. only if this label is not
* resolved yet. For backward references, the offset of the reference
* can be, and must be, computed and stored directly.
*
* @param sourcePosition the position of the referencing instruction.
* This position will be used to compute the
* offset of this forward reference.
* @param referencePosition the position where the offset for this
* forward reference must be stored.
*/
private void addReference(
final int sourcePosition,
final int referencePosition){
312 CHAPTER 7. JVM/CLOJURE/ASM/
if(srcAndRefPositions == null)
{
srcAndRefPositions = new int[6];
}
if(referenceCount >= srcAndRefPositions.length)
{
int[] a = new int[srcAndRefPositions.length + 6];
System.arraycopy(srcAndRefPositions,
0,
a,
0,
srcAndRefPositions.length);
srcAndRefPositions = a;
}
srcAndRefPositions[referenceCount++] = sourcePosition;
srcAndRefPositions[referenceCount++] = referencePosition;
}
/**
* Resolves all forward references to this label. This method must be
* called when this label is added to the bytecode of the method,
* i.e. when its position becomes known. This method fills in the
* blanks that where left in the bytecode by each forward reference
* previously added to this label.
*
* @param owner the code writer that calls this method.
* @param position the position of this label in the bytecode.
* @param data the bytecode of the method.
* @return <tt>true</tt> if a blank that was left for this label was
* to small to store the offset. In such a case the
* corresponding jump instruction is replaced with a pseudo
* instruction (using unused opcodes) using an unsigned two
* bytes offset. These pseudo instructions will need to be
* replaced with true instructions with wider offsets (4 bytes
* instead of 2). This is done in
* {@link MethodWriter#resizeInstructions}.
* @throws IllegalArgumentException if this label has already been
* resolved, or if it has not been
created by the given code writer.
*/
boolean resolve(
final MethodWriter owner,
final int position,
final byte[] data){
boolean needUpdate = false;
this.status |= RESOLVED;
this.position = position;
int i = 0;
while(i < referenceCount)
{
7.15. LABEL.JAVA 313
/**
* Returns the first label of the series to which this label belongs.
* For an isolated label or for the first label in a series of
* successive labels, this method returns the label itself. For other
314 CHAPTER 7. JVM/CLOJURE/ASM/
// -------------------------------------------------------------------
// Overriden Object methods
// -------------------------------------------------------------------
/**
* Returns a string representation of this label.
*
* @return a string representation of this label.
*/
public String toString(){
return "L" + System.identityHashCode(this);
}
}
7.16 MethodAdapter.java
(MethodVisitor [317])
MethodAdapter.java
/**
* An empty {@link MethodVisitor} that delegates to another
* {@link MethodVisitor}. This class can be used as a super class to
* quickly implement usefull method adapter classes, just by overriding
* the necessary methods.
*
* @author Eric Bruneton
*/
public class MethodAdapter implements MethodVisitor{
/**
* The {@link MethodVisitor} to which this adapter delegates calls.
*/
protected MethodVisitor mv;
/**
7.16. METHODADAPTER.JAVA 315
7.17 MethodVisitor.java
MethodVisitor.java
package clojure.asm;
/**
* A visitor to visit a Java method. The methods of this interface must
* be called in the following order:
* [ <tt>visitAnnotationDefault</tt> ] ( * <tt>visitAnnotation</tt> |
* <tt>visitParameterAnnotation</tt> | * <tt>visitAttribute</tt> )*
* [ <tt>visitCode</tt> ( <tt>visitFrame</tt> |
* <tt>visit</tt><i>X</i>Insn</tt> | <tt>visitLabel</tt> |
* <tt>visitTryCatchBlock</tt> | * <tt>visitLocalVariable</tt> |
* <tt>visitLineNumber</tt>)* <tt>visitMaxs</tt> ]
* <tt>visitEnd</tt>. In addition, the <tt>visit</tt><i>X</i>Insn</tt>
* and <tt>visitLabel</tt> methods must be called in the sequential
* order of the bytecode instructions of the visited code,
* <tt>visitTryCatchBlock</tt> must be called <i>before</i> the
* labels passed as arguments have been visited, and the
* <tt>visitLocalVariable</tt> and <tt>visitLineNumber</tt> methods
* must be called <i>after</i> the labels passed as arguments have
* been visited.
*
* @author Eric Bruneton
*/
public interface MethodVisitor{
// --------------------------------------------------------------------
// Annotations and non standard attributes
// --------------------------------------------------------------------
/**
* Visits the default value of this annotation interface method.
*
* @return a visitor to the visit the actual default value of this
* annotation interface method, or <tt>null</tt> if this
* visitor is not interested in visiting this default value.
* The name parameters passed to the methods of this
* annotation visitor are ignored. Moreover, exacly one visit
* method must be called on this annotation visitor, followed
* by visitEnd.
*/
AnnotationVisitor visitAnnotationDefault();
/**
* Visits an annotation of this method.
*
* @param desc the class descriptor of the annotation class.
* @param visible <tt>true</tt> if the annotation is visible at runtime.
* @return a visitor to visit the annotation values, or <tt>null</tt> if
* this visitor is not interested in visiting this annotation.
*/
AnnotationVisitor visitAnnotation(String desc, boolean visible);
7.17. METHODVISITOR.JAVA 319
/**
* Visits an annotation of a parameter this method.
*
* @param parameter the parameter index.
* @param desc the class descriptor of the annotation class.
* @param visible <tt>true</tt> if the annotation is visible at
* runtime.
* @return a visitor to visit the annotation values, or <tt>null</tt>
* if this visitor is not interested in visiting this annotation.
*/
AnnotationVisitor visitParameterAnnotation(
int parameter,
String desc,
boolean visible);
/**
* Visits a non standard attribute of this method.
*
* @param attr an attribute.
*/
void visitAttribute(Attribute attr);
/**
* Starts the visit of the methods code, if any (i.e. non abstract
* method).
*/
void visitCode();
/**
* Visits the current state of the local variables and operand stack
* elements. This method must(*) be called <i>just before</i> any
* instruction <b>i</b> that follows an unconditionnal branch
* instruction such as GOTO or THROW, that is the target of a jump
* instruction, or that starts an exception handler block. The visited
* types must describe the values of the local variables and of the
* operand stack elements <i>just before</i> <b>i</b> is executed.
* <br> <br> (*) this is mandatory only for classes whose version
* is greater than or equal to {@link Opcodes#V1_6 V1_6}. <br> <br>
* Packed frames are basically "deltas" from the state of the
* previous frame (very first frame is implicitly defined by the
* methods parameters and access flags): <ul> <li>
* {@link Opcodes#F_SAME} representing frame with exactly the same
* locals as the previous frame and with the empty stack.</li>
* <li>{@link Opcodes#F_SAME1}
* representing frame with exactly the same locals as the previous
* frame and with single value on the stack (<code>nStack</code> is 1
* and <code>stack[0]</code> contains value for the type of the
* stack item).</li> <li>{@link Opcodes#F_APPEND} representing frame
* with current locals are the same as the locals in the previous
320 CHAPTER 7. JVM/CLOJURE/ASM/
// --------------------------------------------------------------------
// Normal instructions
// --------------------------------------------------------------------
/**
* Visits a zero operand instruction.
*
* @param opcode the opcode of the instruction to be visited. This
7.17. METHODVISITOR.JAVA 321
/**
* Visits an instruction with a single int operand.
*
* @param opcode the opcode of the instruction to be visited. This
* opcode is either BIPUSH, SIPUSH or NEWARRAY.
* @param operand the operand of the instruction to be visited.<br> When
* opcode is BIPUSH, operand value should be between
* Byte.MIN_VALUE and Byte.MAX_VALUE.<br> When opcode
* is SIPUSH, operand value should be between
* Short.MIN_VALUE and Short.MAX_VALUE.<br> When opcode
* is NEWARRAY, operand value should be one of
* {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR},
* {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE},
* {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT},
* {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
*/
void visitIntInsn(int opcode, int operand);
/**
* Visits a local variable instruction. A local variable instruction is
* an instruction that loads or stores the value of a local variable.
*
* @param opcode the opcode of the local variable instruction to be
* visited. This opcode is either ILOAD, LLOAD, FLOAD,
* DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE
* or RET.
* @param var the operand of the instruction to be visited. This
* operand is the index of a local variable.
*/
void visitVarInsn(int opcode, int var);
322 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Visits a type instruction. A type instruction is an instruction that
* takes a type descriptor as parameter.
*
* @param opcode the opcode of the type instruction to be visited.
* This opcode is either NEW, ANEWARRAY, CHECKCAST or
* INSTANCEOF.
* @param desc the operand of the instruction to be visited. This
* operand is must be a fully qualified class name in
* internal form, or the type descriptor of an array
* type (see {@link Type Type}).
*/
void visitTypeInsn(int opcode, String desc);
/**
* Visits a field instruction. A field instruction is an instruction
* that loads or stores the value of a field of an object.
*
* @param opcode the opcode of the type instruction to be visited.
* This opcode is either GETSTATIC, PUTSTATIC, GETFIELD
* or PUTFIELD.
* @param owner the internal name of the fields owner class (see {@link
* Type#getInternalName() getInternalName}).
* @param name the fields name.
* @param desc the fields descriptor (see {@link Type Type}).
*/
void visitFieldInsn(int opcode, String owner, String name, String desc);
/**
* Visits a method instruction. A method instruction is an instruction
* that invokes a method.
*
* @param opcode the opcode of the type instruction to be visited.
* This opcode is either INVOKEVIRTUAL, INVOKESPECIAL,
* INVOKESTATIC or INVOKEINTERFACE.
* @param owner the internal name of the methods owner class (see
* {@link Type#getInternalName() getInternalName}).
* @param name the methods name.
* @param desc the methods descriptor (see {@link Type Type}).
*/
void visitMethodInsn(int opcode, String owner, String name, String desc);
/**
* Visits a jump instruction. A jump instruction is an instruction that
* may jump to another instruction.
*
* @param opcode the opcode of the type instruction to be visited. This
* opcode is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE,
* IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT,
* IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or
7.17. METHODVISITOR.JAVA 323
* IFNONNULL.
* @param label the operand of the instruction to be visited. This
* operand is a label that designates the instruction
* to which the jump instruction may jump.
*/
void visitJumpInsn(int opcode, Label label);
/**
* Visits a label. A label designates the instruction that will be
* visited just after it.
*
* @param label a {@link Label Label} object.
*/
void visitLabel(Label label);
// --------------------------------------------------------------------
// Special instructions
// --------------------------------------------------------------------
/**
* Visits a LDC instruction.
*
* @param cst the constant to be loaded on the stack. This parameter
* must be a non null {@link Integer}, a {@link Float},
* a {@link Long}, a {@link Double} a {@link String} (or
* a {@link Type} for <tt>.class</tt> constants, for classes
* whose version is 49.0 or more).
*/
void visitLdcInsn(Object cst);
/**
* Visits an IINC instruction.
*
* @param var index of the local variable to be incremented.
* @param increment amount to increment the local variable by.
*/
void visitIincInsn(int var, int increment);
/**
* Visits a TABLESWITCH instruction.
*
* @param min the minimum key value.
* @param max the maximum key value.
* @param dflt beginning of the default handler block.
* @param labels beginnings of the handler blocks. <tt>labels[i]</tt>
* is the beginning of the handler block for the
* <tt>min + i</tt> key.
*/
void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]);
324 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Visits a LOOKUPSWITCH instruction.
*
* @param dflt beginning of the default handler block.
* @param keys the values of the keys.
* @param labels beginnings of the handler blocks. <tt>labels[i]</tt>
* is the beginning of the handler block for the
* <tt>keys[i]</tt> key.
*/
void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]);
/**
* Visits a MULTIANEWARRAY instruction.
*
* @param desc an array type descriptor (see {@link Type Type}).
* @param dims number of dimensions of the array to allocate.
*/
void visitMultiANewArrayInsn(String desc, int dims);
// ---------------------------------------------------------------
// Exceptions table entries, debug information, max stack and max
// locals
// ---------------------------------------------------------------
/**
* Visits a try catch block.
*
* @param start beginning of the exception handlers scope (inclusive).
* @param end end of the exception handlers scope (exclusive).
* @param handler beginning of the exception handlers code.
* @param type internal name of the type of exceptions handled by the
* handler, or <tt>null</tt> to catch any exceptions
* (for "finally" blocks).
* @throws IllegalArgumentException if one of the labels has already
* been visited by this visitor (by the
* {@link #visitLabel visitLabel} method).
*/
void visitTryCatchBlock(Label start,
Label end,
Label handler,
String type);
/**
* Visits a local variable declaration.
*
* @param name the name of a local variable.
* @param desc the type descriptor of this local variable.
* @param signature the type signature of this local variable. May be
* <tt>null</tt> if the local variable type does not
* use generic types.
7.17. METHODVISITOR.JAVA 325
/**
* Visits a line number declaration.
*
* @param line a line number. This number refers to the source file
* from which the class was compiled.
* @param start the first instruction corresponding to this line number.
* @throws IllegalArgumentException if <tt>start</tt> has not already
* been visited by this visitor (by the
* {@link #visitLabel visitLabel} method).
*/
void visitLineNumber(int line, Label start);
/**
* Visits the maximum stack size and the maximum number of local
* variables of the method.
*
* @param maxStack maximum stack size of the method.
* @param maxLocals maximum number of local variables for the method.
*/
void visitMaxs(int maxStack, int maxLocals);
/**
* Visits the end of the method. This method, which is the last one to
* be called, is used to inform the visitor that all the annotations and
* attributes of the method have been visited.
*/
void visitEnd();
}
-
326 CHAPTER 7. JVM/CLOJURE/ASM/
7.18 MethodWriter.java
(MethodVisitor [317])
MethodWriter.java
/**
* A {@link MethodVisitor} that generates methods in bytecode form.
* Each visit method of this class appends the bytecode corresponding
* to the visited instruction to a byte vector, in the order these
* methods are called.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
class MethodWriter implements MethodVisitor{
/**
* Pseudo access flag used to denote constructors.
*/
final static int ACC_CONSTRUCTOR = 262144;
/**
* Frame has exactly the same locals as the previous stack map frame
* and number of stack items is zero.
*/
final static int SAME_FRAME = 0; // to 63 (0-3f)
/**
* Frame has exactly the same locals as the previous stack map frame
* and number of stack items is 1
*/
final static int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f)
/**
* Reserved for future use
*/
final static int RESERVED = 128;
/**
* Frame has exactly the same locals as the previous stack map frame
* and number of stack items is 1. Offset is bigger then 63;
*/
final static int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7
/**
* Frame where current locals are the same as the locals in the previous
7.18. METHODWRITER.JAVA 327
* frame, except that the k last locals are absent. The value of k is
* given by the formula 251-frame_type.
*/
final static int CHOP_FRAME = 248; // to 250 (f8-fA)
/**
* Frame has exactly the same locals as the previous stack map frame
* and number of stack items is zero. Offset is bigger then 63;
*/
final static int SAME_FRAME_EXTENDED = 251; // fb
/**
* Frame where current locals are the same as the locals in the
* previous frame, except that k additional locals are defined.
* The value of k is given by the formula frame_type-251.
*/
final static int APPEND_FRAME = 252; // to 254 // fc-fe
/**
* Full frame
*/
final static int FULL_FRAME = 255; // ff
/**
* Indicates that the stack map frames must be recomputed from scratch.
* In this case the maximum stack size and number of local variables
* is also recomputed from scratch.
*
* @see #compute
*/
private final static int FRAMES = 0;
/**
* Indicates that the maximum stack size and number of local variables
* must be automatically computed.
*
* @see #compute
*/
private final static int MAXS = 1;
/**
* Indicates that nothing must be automatically computed.
*
* @see #compute
*/
private final static int NOTHING = 2;
/**
* Next method writer (see {@link ClassWriter#firstMethod firstMethod}).
*/
328 CHAPTER 7. JVM/CLOJURE/ASM/
MethodWriter next;
/**
* The class writer to which this method must be added.
*/
ClassWriter cw;
/**
* Access flags of this method.
*/
private int access;
/**
* The index of the constant pool item that contains the name of this
* method.
*/
private int name;
/**
* The index of the constant pool item that contains the descriptor
* of this method.
*/
private int desc;
/**
* The descriptor of this method.
*/
private String descriptor;
/**
* The signature of this method.
*/
String signature;
/**
* If not zero, indicates that the code of this method must be copied
* from the ClassReader associated to this writer in
* <code>cw.cr</code>. More precisely, this field gives the index of
* the first byte to copied from <code>cw.cr.b</code>.
*/
int classReaderOffset;
/**
* If not zero, indicates that the code of this method must be
* copied from the ClassReader associated to this writer in
* <code>cw.cr</code>. More precisely, this field gives the number
* of bytes to copied from <code>cw.cr.b</code>.
*/
int classReaderLength;
7.18. METHODWRITER.JAVA 329
/**
* Number of exceptions that can be thrown by this method.
*/
int exceptionCount;
/**
* The exceptions that can be thrown by this method. More precisely,
* this array contains the indexes of the constant pool items that
* contain the internal names of these exception classes.
*/
int[] exceptions;
/**
* The annotation default attribute of this method. May be
* <tt>null</tt>.
*/
private ByteVector annd;
/**
* The runtime visible annotations of this method. May be
* <tt>null</tt>.
*/
private AnnotationWriter anns;
/**
* The runtime invisible annotations of this method. May be
* <tt>null</tt>.
*/
private AnnotationWriter ianns;
/**
* The runtime visible parameter annotations of this method. May be
* <tt>null</tt>.
*/
private AnnotationWriter[] panns;
/**
* The runtime invisible parameter annotations of this method. May be
* <tt>null</tt>.
*/
private AnnotationWriter[] ipanns;
/**
* The non standard attributes of the method.
*/
private Attribute attrs;
/**
* The bytecode of this method.
*/
330 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Maximum stack size of this method.
*/
private int maxStack;
/**
* Maximum number of local variables for this method.
*/
private int maxLocals;
/**
* Number of stack map frames in the StackMapTable attribute.
*/
private int frameCount;
/**
* The StackMapTable attribute.
*/
private ByteVector stackMap;
/**
* The offset of the last frame that was written in the StackMapTable
* attribute.
*/
private int previousFrameOffset;
/**
* The last frame that was written in the StackMapTable attribute.
*
* @see #frame
*/
private int[] previousFrame;
/**
* Index of the next element to be added in {@link #frame}.
*/
private int frameIndex;
/**
* The current stack map frame. The first element contains the offset
* of the instruction to which the frame corresponds, the second
* element is the number of locals and the third one is the number
* of stack elements. The local variables start at index 3 and are
* followed by the operand stack values. In summary frame[0] = offset,
* frame[1] = nLocal, frame[2] = nStack, frame[3] = nLocal. All types
* are encoded as integers, with the same format as the one used in
* {@link Label}, but limited to BASE types.
*/
7.18. METHODWRITER.JAVA 331
/**
* Number of elements in the exception handler list.
*/
private int handlerCount;
/**
* The first element in the exception handler list.
*/
private Handler firstHandler;
/**
* The last element in the exception handler list.
*/
private Handler lastHandler;
/**
* Number of entries in the LocalVariableTable attribute.
*/
private int localVarCount;
/**
* The LocalVariableTable attribute.
*/
private ByteVector localVar;
/**
* Number of entries in the LocalVariableTypeTable attribute.
*/
private int localVarTypeCount;
/**
* The LocalVariableTypeTable attribute.
*/
private ByteVector localVarType;
/**
* Number of entries in the LineNumberTable attribute.
*/
private int lineNumberCount;
/**
* The LineNumberTable attribute.
*/
private ByteVector lineNumber;
/**
* The non standard attributes of the methods code.
*/
332 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Indicates if some jump instructions are too small and need to be
* resized.
*/
private boolean resize;
/**
* Indicates if the instructions contain at least one JSR instruction.
*/
private boolean jsr;
// -------------------------------------------------------------------
/*
* Fields for the control flow graph analysis algorithm (used to
* compute the maximum stack size). A control flow graph contains
* one node per "basic block", and one edge per "jump" from one basic
* block to another. Each node (i.e., each basic block) is represented
* by the Label object that corresponds to the first instruction of
* this basic block. Each node also stores the list of its successors
* in the graph, as a linked list of Edge objects.
*/
/**
* Indicates what must be automatically computed.
*
* @see FRAMES
* @see MAXS
* @see NOTHING
*/
private int compute;
/**
* A list of labels. This list is the list of basic blocks in the
* method, i.e. a list of Label objects linked to each other by their
* {@link Label#successor} field, in the order they are visited by
* {@link visitLabel}, and starting with the first basic block.
*/
private Label labels;
/**
* The previous basic block.
*/
private Label previousBlock;
/**
* The current basic block.
*/
7.18. METHODWRITER.JAVA 333
/**
* The (relative) stack size after the last visited instruction. This
* size is relative to the beginning of the current basic block, i.e.,
* the true stack size after the last visited instruction is equal to
* the {@link Label#inputStackTop beginStackSize} of the current basic
* block plus <tt>stackSize</tt>.
*/
private int stackSize;
/**
* The (relative) maximum stack size after the last visited instruction.
* This size is relative to the beginning of the current basic block,
* i.e., the true maximum stack size after the last visited instruction
* is equal to the {@link Label#inputStackTop beginStackSize} of the
* current basic block plus <tt>stackSize</tt>.
*/
private int maxStackSize;
// -------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------
/**
* Constructs a new {@link MethodWriter}.
*
* @param cw the class writer in which the method must be
* added.
* @param access the methods access flags (see {@link Opcodes}).
* @param name the methods name.
* @param desc the methods descriptor (see {@link Type}).
* @param signature the methods signature. May be <tt>null</tt>.
* @param exceptions the internal names of the methods exceptions.
* May be <tt>null</tt>.
* @param computeMaxs <tt>true</tt> if the maximum stack size and
* number of local variables must be automatically
* computed.
* @param computeFrames <tt>true</tt> if the stack map tables must be
* recomputed from scratch.
*/
MethodWriter(
final ClassWriter cw,
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions,
final boolean computeMaxs,
final boolean computeFrames){
334 CHAPTER 7. JVM/CLOJURE/ASM/
if(cw.firstMethod == null)
{
cw.firstMethod = this;
}
else
{
cw.lastMethod.next = this;
}
cw.lastMethod = this;
this.cw = cw;
this.access = access;
this.name = cw.newUTF8(name);
this.desc = cw.newUTF8(desc);
this.descriptor = desc;
this.signature = signature;
if(exceptions != null && exceptions.length > 0)
{
exceptionCount = exceptions.length;
this.exceptions = new int[exceptionCount];
for(int i = 0; i < exceptionCount; ++i)
{
this.exceptions[i] = cw.newClass(exceptions[i]);
}
}
this.compute =
computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING);
if(computeMaxs || computeFrames)
{
if(computeFrames && name.equals("<init>"))
{
this.access |= ACC_CONSTRUCTOR;
}
// updates maxLocals
int size = getArgumentsAndReturnSizes(descriptor) >> 2;
if((access & Opcodes.ACC_STATIC) != 0)
{
--size;
}
maxLocals = size;
// creates and visits the label for the first basic block
labels = new Label();
labels.status |= Label.PUSHED;
visitLabel(labels);
}
}
// -------------------------------------------------------------------
// Implementation of the MethodVisitor interface
// -------------------------------------------------------------------
7.18. METHODWRITER.JAVA 335
Type.getArgumentTypes(descriptor).length];
}
aw.next = ipanns[parameter];
ipanns[parameter] = aw;
}
return aw;
}
if(type == Opcodes.F_NEW)
{
startFrame(code.length, nLocal, nStack);
for(int i = 0; i < nLocal; ++i)
{
if(local[i] instanceof String)
{
frame[frameIndex++] = Frame.OBJECT
| cw.addType((String) local[i]);
}
else if(local[i] instanceof Integer)
{
frame[frameIndex++] = ((Integer) local[i]).intValue();
}
else
7.18. METHODWRITER.JAVA 337
{
frame[frameIndex++] =
Frame.UNINITIALIZED
| cw.addUninitializedType("",
((Label) local[i]).position);
}
}
for(int i = 0; i < nStack; ++i)
{
if(stack[i] instanceof String)
{
frame[frameIndex++] = Frame.OBJECT
| cw.addType((String) stack[i]);
}
else if(stack[i] instanceof Integer)
{
frame[frameIndex++] = ((Integer) stack[i]).intValue();
}
else
{
frame[frameIndex++] =
Frame.UNINITIALIZED
| cw.addUninitializedType("",
((Label) stack[i]).position);
}
}
endFrame();
}
else
{
int delta;
if(stackMap == null)
{
stackMap = new ByteVector();
delta = code.length;
}
else
{
delta = code.length - previousFrameOffset - 1;
}
switch(type)
{
case Opcodes.F_FULL:
stackMap.putByte(FULL_FRAME)
.putShort(delta)
.putShort(nLocal);
for(int i = 0; i < nLocal; ++i)
{
writeFrameType(local[i]);
338 CHAPTER 7. JVM/CLOJURE/ASM/
}
stackMap.putShort(nStack);
for(int i = 0; i < nStack; ++i)
{
writeFrameType(stack[i]);
}
break;
case Opcodes.F_APPEND:
stackMap.putByte(SAME_FRAME_EXTENDED + nLocal)
.putShort(delta);
for(int i = 0; i < nLocal; ++i)
{
writeFrameType(local[i]);
}
break;
case Opcodes.F_CHOP:
stackMap.putByte(SAME_FRAME_EXTENDED - nLocal)
.putShort(delta);
break;
case Opcodes.F_SAME:
if(delta < 64)
{
stackMap.putByte(delta);
}
else
{
stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
}
break;
case Opcodes.F_SAME1:
if(delta < 64)
{
stackMap.putByte(
SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
}
else
{
stackMap.putByte(
SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
.putShort(delta);
}
writeFrameType(stack[0]);
break;
}
previousFrameOffset = code.length;
++frameCount;
}
}
7.18. METHODWRITER.JAVA 339
}
// adds the instruction to the bytecode of the method
if(opcode == Opcodes.SIPUSH)
{
code.put12(opcode, operand);
}
else
{ // BIPUSH or NEWARRAY
code.put11(opcode, operand);
}
}
{
n = var + 2;
}
else
{
n = var + 1;
}
if(n > maxLocals)
{
maxLocals = n;
}
}
// adds the instruction to the bytecode of the method
if(var < 4 && opcode != Opcodes.RET)
{
int opt;
if(opcode < Opcodes.ISTORE)
{
/* ILOAD_0 */
opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var;
}
else
{
/* ISTORE_0 */
opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var;
}
code.putByte(opt);
}
else if(var >= 256)
{
code.putByte(196 /* WIDE */).put12(opcode, var);
}
else
{
code.put11(opcode, var);
}
if(opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0)
{
visitLabel(new Label());
}
}
}
else if(opcode == Opcodes.NEW)
{
// updates current and max stack sizes only if opcode == NEW
// (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
int size = stackSize + 1;
if(size > maxStackSize)
{
maxStackSize = size;
}
stackSize = size;
}
}
// adds the instruction to the bytecode of the method
code.put12(opcode, i.index);
}
}
// updates current and max stack sizes
if(size > maxStackSize)
{
maxStackSize = size;
}
stackSize = size;
}
}
// adds the instruction to the bytecode of the method
code.put12(opcode, i.index);
}
{
if(opcode == Opcodes.JSR)
{
jsr = true;
currentBlock.status |= Label.JSR;
addSuccessor(stackSize + 1, label);
// creates a Label for the next basic block
nextInsn = new Label();
/*
* note that, by construction in this method, a JSR block
* has at least two successors in the control flow graph:
* the first one leads the next instruction after the
* JSR, while the second one leads to the JSR target.
*/
}
else
{
// updates current stack size (max stack size unchanged
// because stack size variation always negative in this
// case)
stackSize += Frame.SIZE[opcode];
addSuccessor(stackSize, label);
}
}
}
// adds the instruction to the bytecode of the method
if((label.status & Label.RESOLVED) != 0
&& label.position - code.length < Short.MIN_VALUE)
{
/*
* case of a backward jump with an offset < -32768. In this case
* we automatically replace GOTO with GOTO_W, JSR with JSR_W
* and IFxxx <l> with IFNOTxxx <l> GOTO_W <l>, where IFNOTxxx
* is the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and
* where <l> designates the instruction just after the GOTO_W.
*/
if(opcode == Opcodes.GOTO)
{
code.putByte(200); // GOTO_W
}
else if(opcode == Opcodes.JSR)
{
code.putByte(201); // JSR_W
}
else
{
// if the IF instruction is transformed into IFNOT GOTO_W the
// next instruction becomes the target of the IFNOT
// instruction
if(nextInsn != null)
346 CHAPTER 7. JVM/CLOJURE/ASM/
{
nextInsn.status |= Label.TARGET;
}
code.putByte(opcode <= 166
? ((opcode + 1) ^ 1) - 1
: opcode ^ 1);
code.putShort(8); // jump offset
code.putByte(200); // GOTO_W
}
label.put(this, code, code.length - 1, true);
}
else
{
/*
* case of a backward jump with an offset >= -32768, or of a
* forward jump with, of course, an unknown offset. In these
* cases we store the offset in 2 bytes (which will be
* increased in resizeInstructions, if needed).
*/
code.putByte(opcode);
label.put(this, code, code.length - 1, false);
}
if(currentBlock != null)
{
if(nextInsn != null)
{
// if the jump instruction is not a GOTO, the next
// instruction is also a successor of this instruction.
// Calling visitLabel adds the label of this next
// instruction as a successor of the current block,
// and starts a new basic block
visitLabel(nextInsn);
}
if(opcode == Opcodes.GOTO)
{
noSuccessor();
}
}
}
if(currentBlock != null)
{
if(label.position == currentBlock.position)
{
// successive labels, do not start a new basic block
currentBlock.status |= (label.status & Label.TARGET);
label.frame = currentBlock.frame;
return;
}
// ends current block (with one new successor)
addSuccessor(Edge.NORMAL, label);
}
// begins a new current block
currentBlock = label;
if(label.frame == null)
{
label.frame = new Frame();
label.frame.owner = label;
}
// updates the basic block list
if(previousBlock != null)
{
if(label.position == previousBlock.position)
{
previousBlock.status |= (label.status & Label.TARGET);
label.frame = previousBlock.frame;
currentBlock = previousBlock;
return;
}
previousBlock.successor = label;
}
previousBlock = label;
}
else if(compute == MAXS)
{
if(currentBlock != null)
{
// ends current block (with one new successor)
currentBlock.outputStackMax = maxStackSize;
addSuccessor(stackSize, label);
}
// begins a new current block
currentBlock = label;
// resets the relative current and max stack sizes
stackSize = 0;
maxStackSize = 0;
// updates the basic block list
if(previousBlock != null)
{
previousBlock.successor = label;
348 CHAPTER 7. JVM/CLOJURE/ASM/
}
previousBlock = label;
}
}
{
// completes the control flow graph with exception handler blocks
Handler handler = firstHandler;
while(handler != null)
{
Label l = handler.start.getFirst();
Label h = handler.handler.getFirst();
Label e = handler.end.getFirst();
// computes the kind of the edges to h
String t = handler.desc == null
? "java/lang/Throwable"
: handler.desc;
int kind = Frame.OBJECT | cw.addType(t);
// h is an exception handler
h.status |= Label.TARGET;
// adds h as a successor of labels between start
// and end
while(l != e)
{
// creates an edge to h
Edge b = new Edge();
b.info = kind;
b.successor = h;
// adds it to the successors of l
b.next = l.successors;
l.successors = b;
// goes to the next label
l = l.successor;
}
handler = handler.next;
}
/*
* fix point algorithm: mark the first basic block as changed
* (i.e. put it in the changed list) and, while there are
* changed basic blocks, choose one, mark it as unchanged,
* and update its successors (which can be changed in the
* process).
*/
int max = 0;
Label changed = labels;
while(changed != null)
{
// removes a basic block from the list of changed basic
354 CHAPTER 7. JVM/CLOJURE/ASM/
// blocks
Label l = changed;
changed = changed.next;
l.next = null;
f = l.frame;
// a reacheable jump target must be stored in the stack map
if((l.status & Label.TARGET) != 0)
{
l.status |= Label.STORE;
}
// all visited labels are reacheable, by definition
l.status |= Label.REACHABLE;
// updates the (absolute) maximum stack size
int blockMax = f.inputStack.length + l.outputStackMax;
if(blockMax > max)
{
max = blockMax;
}
// updates the successors of the current basic block
Edge e = l.successors;
while(e != null)
{
Label n = e.successor.getFirst();
boolean change = f.merge(cw, n.frame, e.info);
if(change && n.next == null)
{
// if n has changed and is not already in the
// changed list, adds it to this list
n.next = changed;
changed = n;
}
e = e.next;
}
}
this.maxStack = max;
// visits all the frames that must be stored in the stack map
Label l = labels;
while(l != null)
{
f = l.frame;
if((l.status & Label.STORE) != 0)
{
visitFrame(f);
}
if((l.status & Label.REACHABLE) == 0)
{
// finds start and end of dead basic block
Label k = l.successor;
int start = l.position;
7.18. METHODWRITER.JAVA 355
l.successors = b;
}
// goes to the next label
l = l.successor;
}
handler = handler.next;
}
if(jsr)
{
// completes the control flow graph with the RET successors
/*
* first step: finds the subroutines. This step determines,
* for each basic block, to which subroutine(s) it belongs,
* and stores this set as a bit set in the
* {@link Label#status} field. Subroutines are numbered
* with powers of two, from 0x1000 to 0x80000000 (so there
* must be at most 20 subroutines in a method).
*/
// finds the basic blocks that belong to the "main"
// subroutine
int id = 0x1000;
findSubroutine(labels, id);
// finds the basic blocks that belong to the real subroutines
Label l = labels;
while(l != null)
{
if((l.status & Label.JSR) != 0)
{
// the subroutine is defined by ls TARGET, not by l
Label subroutine = l.successors.next.successor;
// if this subroutine does not have an id yet...
if((subroutine.status & ~0xFFF) == 0)
{
// ...assigns it a new id and finds its
// basic blocks
id = id << 1;
findSubroutine(subroutine, id);
}
}
l = l.successor;
}
// second step: finds the successors of RET blocks
findSubroutineSuccessors(0x1000, new Label[10], 0);
}
/*
* control flow analysis algorithm: while the block stack is not
* empty, pop a block from this stack, update the max stack size,
* compute the true (non relative) begin stack size of the
7.18. METHODWRITER.JAVA 357
{
this.maxStack = maxStack;
this.maxLocals = maxLocals;
}
}
// -------------------------------------------------------------------
// Utility methods: control flow analysis algorithm
// -------------------------------------------------------------------
/**
* Computes the size of the arguments and of the return value of a
* method.
*
* @param desc the descriptor of a method.
* @return the size of the arguments of the method (plus one for the
* implicit this argument), argSize, and the size of its return
* value, retSize, packed into a single int i =
* <tt>(argSize << 2) | retSize</tt> (argSize is therefore equal
* to <tt>i >> 2</tt>, and retSize to <tt>i & 0x03</tt>).
*/
static int getArgumentsAndReturnSizes(final String desc){
int n = 1;
int c = 1;
while(true)
{
char car = desc.charAt(c++);
if(car == ))
{
car = desc.charAt(c);
return n << 2
| (car == V ? 0 : (car == D || car == J ? 2 : 1));
}
else if(car == L)
{
while(desc.charAt(c++) != ;)
{
}
n += 1;
}
else if(car == [)
{
while((car = desc.charAt(c)) == [)
{
++c;
}
if(car == D || car == J)
7.18. METHODWRITER.JAVA 359
{
n -= 1;
}
}
else if(car == D || car == J)
{
n += 2;
}
else
{
n += 1;
}
}
}
/**
* Adds a successor to the {@link #currentBlock currentBlock} block.
*
* @param info information about the control flow edge to be added.
* @param successor the successor block to be added to the current block.
*/
private void addSuccessor(final int info, final Label successor){
// creates and initializes an Edge object...
Edge b = new Edge();
b.info = info;
b.successor = successor;
// ...and adds it to the successor list of the currentBlock block
b.next = currentBlock.successors;
currentBlock.successors = b;
}
/**
* Ends the current basic block. This method must be used in the case
* where the current basic block does not have any successor.
*/
private void noSuccessor(){
if(compute == FRAMES)
{
Label l = new Label();
l.frame = new Frame();
l.frame.owner = l;
l.resolve(this, code.length, code.data);
previousBlock.successor = l;
previousBlock = l;
}
else
{
currentBlock.outputStackMax = maxStackSize;
}
currentBlock = null;
360 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Finds the basic blocks that belong to a given subroutine, and marks
* these blocks as belonging to this subroutine (by using
* {@link Label#status} as a bit set (see {@link #visitMaxs}). This
* recursive method follows the control flow graph to find all the
* blocks that are reachable from the given block WITHOUT following
* any JSR target.
*
* @param block a block that belongs to the subroutine
* @param id the id of this subroutine
*/
private void findSubroutine(final Label block, final int id){
// if block is already marked as belonging to subroutine id,
// returns
if((block.status & id) != 0)
{
return;
}
// marks block as belonging to subroutine id
block.status |= id;
// calls this method recursively on each successor, except
// JSR targets
Edge e = block.successors;
while(e != null)
{
// if block is a JSR block, then block.successors.next
// leads to the JSR target (see {@link #visitJumpInsn}) and
// must therefore not be followed
if((block.status & Label.JSR) == 0 || e != block.successors.next)
{
findSubroutine(e.successor, id);
}
e = e.next;
}
}
/**
* Finds the successors of the RET blocks of the specified subroutine,
* and of any nested subroutine it calls.
*
* @param id id of the subroutine whose RET block successors must
* be found.
* @param JSRs the JSR blocks that were followed to reach this
* subroutine.
* @param nJSRs number of JSR blocks in the JSRs array.
*/
private void findSubroutineSuccessors(
final int id,
7.18. METHODWRITER.JAVA 361
l.successors = e;
break;
}
}
}
}
l = l.successor;
}
}
// -------------------------------------------------------------------
// Utility methods: stack map frames
// -------------------------------------------------------------------
/**
* Visits a frame that has been computed from scratch.
*
* @param f the frame that must be visited.
*/
private void visitFrame(final Frame f){
int i, t;
int nTop = 0;
int nLocal = 0;
int nStack = 0;
int[] locals = f.inputLocals;
int[] stacks = f.inputStack;
// computes the number of locals (ignores TOP types that are just
// after a LONG or a DOUBLE, and all trailing TOP types)
for(i = 0; i < locals.length; ++i)
{
t = locals[i];
if(t == Frame.TOP)
{
++nTop;
}
else
{
nLocal += nTop + 1;
nTop = 0;
}
if(t == Frame.LONG || t == Frame.DOUBLE)
{
++i;
}
}
// computes the stack size (ignores TOP types that are just after
// a LONG or a DOUBLE)
for(i = 0; i < stacks.length; ++i)
{
t = stacks[i];
7.18. METHODWRITER.JAVA 363
++nStack;
if(t == Frame.LONG || t == Frame.DOUBLE)
{
++i;
}
}
// visits the frame and its content
startFrame(f.owner.position, nLocal, nStack);
for(i = 0; nLocal > 0; ++i, --nLocal)
{
t = locals[i];
frame[frameIndex++] = t;
if(t == Frame.LONG || t == Frame.DOUBLE)
{
++i;
}
}
for(i = 0; i < stacks.length; ++i)
{
t = stacks[i];
frame[frameIndex++] = t;
if(t == Frame.LONG || t == Frame.DOUBLE)
{
++i;
}
}
endFrame();
}
/**
* Starts the visit of a stack map frame.
*
* @param offset the offset of the instruction to which the frame
* corresponds.
* @param nLocal the number of local variables in the frame.
* @param nStack the number of stack elements in the frame.
*/
private void startFrame(final int offset,
final int nLocal,
final int nStack){
int n = 3 + nLocal + nStack;
if(frame == null || frame.length < n)
{
frame = new int[n];
}
frame[0] = offset;
frame[1] = nLocal;
frame[2] = nStack;
frameIndex = 3;
}
364 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Checks if the visit of the current frame {@link #frame} is finished,
* and if yes, write it in the StackMapTable attribute.
*/
private void endFrame(){
if(previousFrame != null)
{ // do not write the first frame
if(stackMap == null)
{
stackMap = new ByteVector();
}
writeFrame();
++frameCount;
}
previousFrame = frame;
frame = null;
}
/**
* Compress and writes the current frame {@link #frame} in the
* StackMapTable attribute.
*/
private void writeFrame(){
int clocalsSize = frame[1];
int cstackSize = frame[2];
if((cw.version & 0xFFFF) < Opcodes.V1_6)
{
stackMap.putShort(frame[0]).putShort(clocalsSize);
writeFrameTypes(3, 3 + clocalsSize);
stackMap.putShort(cstackSize);
writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
return;
}
int localsSize = previousFrame[1];
int type = FULL_FRAME;
int k = 0;
int delta;
if(frameCount == 0)
{
delta = frame[0];
}
else
{
delta = frame[0] - previousFrame[0] - 1;
}
if(cstackSize == 0)
{
k = clocalsSize - localsSize;
switch(k)
7.18. METHODWRITER.JAVA 365
{
case-3:
case-2:
case-1:
type = CHOP_FRAME;
localsSize = clocalsSize;
break;
case 0:
type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
break;
case 1:
case 2:
case 3:
type = APPEND_FRAME;
break;
}
}
else if(clocalsSize == localsSize && cstackSize == 1)
{
type = delta < 63
? SAME_LOCALS_1_STACK_ITEM_FRAME
: SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
}
if(type != FULL_FRAME)
{
// verify if locals are the same
int l = 3;
for(int j = 0; j < localsSize; j++)
{
if(frame[l] != previousFrame[l])
{
type = FULL_FRAME;
break;
}
l++;
}
}
switch(type)
{
case SAME_FRAME:
stackMap.putByte(delta);
break;
case SAME_LOCALS_1_STACK_ITEM_FRAME:
stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
break;
case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
.putShort(delta);
writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
366 CHAPTER 7. JVM/CLOJURE/ASM/
break;
case SAME_FRAME_EXTENDED:
stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
break;
case CHOP_FRAME:
stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
break;
case APPEND_FRAME:
stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
writeFrameTypes(3 + localsSize, 3 + clocalsSize);
break;
// case FULL_FRAME:
default:
stackMap.putByte(FULL_FRAME)
.putShort(delta)
.putShort(clocalsSize);
writeFrameTypes(3, 3 + clocalsSize);
stackMap.putShort(cstackSize);
writeFrameTypes(3+clocalsSize, 3+clocalsSize+cstackSize);
}
}
/**
* Writes some types of the current frame {@link #frame} into the
* StackMapTableAttribute. This method converts types from the format
* used in {@link Label} to the format used in StackMapTable attributes.
* In particular, it converts type table indexes to constant pool
* indexes.
*
* @param start index of the first type in {@link #frame} to write.
* @param end index of last type in {@link #frame} to write
* (exclusive).
*/
private void writeFrameTypes(final int start, final int end){
for(int i = start; i < end; ++i)
{
int t = frame[i];
int d = t & Frame.DIM;
if(d == 0)
{
int v = t & Frame.BASE_VALUE;
switch(t & Frame.BASE_KIND)
{
case Frame.OBJECT:
stackMap.putByte(7)
.putShort(cw.newClass(cw.typeTable[v].strVal1));
break;
case Frame.UNINITIALIZED:
stackMap.putByte(8).putShort(cw.typeTable[v].intVal);
break;
7.18. METHODWRITER.JAVA 367
default:
stackMap.putByte(v);
}
}
else
{
StringBuffer buf = new StringBuffer();
d >>= 28;
while(d-- > 0)
{
buf.append([);
}
if((t & Frame.BASE_KIND) == Frame.OBJECT)
{
buf.append(L);
buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1);
buf.append(;);
}
else
{
switch(t & 0xF)
{
case 1:
buf.append(I);
break;
case 2:
buf.append(F);
break;
case 3:
buf.append(D);
break;
case 9:
buf.append(Z);
break;
case 10:
buf.append(B);
break;
case 11:
buf.append(C);
break;
case 12:
buf.append(S);
break;
default:
buf.append(J);
}
}
stackMap.putByte(7).putShort(cw.newClass(buf.toString()));
}
}
368 CHAPTER 7. JVM/CLOJURE/ASM/
// -------------------------------------------------------------------
// Utility methods: dump bytecode array
// -------------------------------------------------------------------
/**
* Returns the size of the bytecode of this method.
*
* @return the size of the bytecode of this method.
*/
final int getSize(){
if(classReaderOffset != 0)
{
return 6 + classReaderLength;
}
if(resize)
{
// replaces the temporary jump opcodes introduced by
// Label.resolve.
resizeInstructions();
}
int size = 8;
if(code.length > 0)
{
cw.newUTF8("Code");
size += 18 + code.length + 8 * handlerCount;
if(localVar != null)
{
cw.newUTF8("LocalVariableTable");
size += 8 + localVar.length;
}
if(localVarType != null)
{
cw.newUTF8("LocalVariableTypeTable");
7.18. METHODWRITER.JAVA 369
size += 8 + localVarType.length;
}
if(lineNumber != null)
{
cw.newUTF8("LineNumberTable");
size += 8 + lineNumber.length;
}
if(stackMap != null)
{
boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
cw.newUTF8(zip ? "StackMapTable" : "StackMap");
size += 8 + stackMap.length;
}
if(cattrs != null)
{
size += cattrs.getSize(cw,
code.data,
code.length,
maxStack,
maxLocals);
}
}
if(exceptionCount > 0)
{
cw.newUTF8("Exceptions");
size += 8 + 2 * exceptionCount;
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
&& (cw.version & 0xffff) < Opcodes.V1_5)
{
cw.newUTF8("Synthetic");
size += 6;
}
if((access & Opcodes.ACC_DEPRECATED) != 0)
{
cw.newUTF8("Deprecated");
size += 6;
}
if(signature != null)
{
cw.newUTF8("Signature");
cw.newUTF8(signature);
size += 8;
}
if(annd != null)
{
cw.newUTF8("AnnotationDefault");
size += 6 + annd.length;
}
if(anns != null)
370 CHAPTER 7. JVM/CLOJURE/ASM/
{
cw.newUTF8("RuntimeVisibleAnnotations");
size += 8 + anns.getSize();
}
if(ianns != null)
{
cw.newUTF8("RuntimeInvisibleAnnotations");
size += 8 + ianns.getSize();
}
if(panns != null)
{
cw.newUTF8("RuntimeVisibleParameterAnnotations");
size += 7 + 2 * panns.length;
for(int i = panns.length - 1; i >= 0; --i)
{
size += panns[i] == null ? 0 : panns[i].getSize();
}
}
if(ipanns != null)
{
cw.newUTF8("RuntimeInvisibleParameterAnnotations");
size += 7 + 2 * ipanns.length;
for(int i = ipanns.length - 1; i >= 0; --i)
{
size += ipanns[i] == null ? 0 : ipanns[i].getSize();
}
}
if(attrs != null)
{
size += attrs.getSize(cw, null, 0, -1, -1);
}
return size;
}
/**
* Puts the bytecode of this method in the given byte vector.
*
* @param out the byte vector into which the bytecode of this method must
* be copied.
*/
final void put(final ByteVector out){
out.putShort(access).putShort(name).putShort(desc);
if(classReaderOffset != 0)
{
out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength);
return;
}
int attributeCount = 0;
if(code.length > 0)
{
7.18. METHODWRITER.JAVA 371
++attributeCount;
}
if(exceptionCount > 0)
{
++attributeCount;
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
&& (cw.version & 0xffff) < Opcodes.V1_5)
{
++attributeCount;
}
if((access & Opcodes.ACC_DEPRECATED) != 0)
{
++attributeCount;
}
if(signature != null)
{
++attributeCount;
}
if(annd != null)
{
++attributeCount;
}
if(anns != null)
{
++attributeCount;
}
if(ianns != null)
{
++attributeCount;
}
if(panns != null)
{
++attributeCount;
}
if(ipanns != null)
{
++attributeCount;
}
if(attrs != null)
{
attributeCount += attrs.getCount();
}
out.putShort(attributeCount);
if(code.length > 0)
{
int size = 12 + code.length + 8 * handlerCount;
if(localVar != null)
{
size += 8 + localVar.length;
372 CHAPTER 7. JVM/CLOJURE/ASM/
}
if(localVarType != null)
{
size += 8 + localVarType.length;
}
if(lineNumber != null)
{
size += 8 + lineNumber.length;
}
if(stackMap != null)
{
size += 8 + stackMap.length;
}
if(cattrs != null)
{
size += cattrs.getSize(cw,
code.data,
code.length,
maxStack,
maxLocals);
}
out.putShort(cw.newUTF8("Code")).putInt(size);
out.putShort(maxStack).putShort(maxLocals);
out.putInt(code.length).putByteArray(code.data, 0, code.length);
out.putShort(handlerCount);
if(handlerCount > 0)
{
Handler h = firstHandler;
while(h != null)
{
out.putShort(h.start.position)
.putShort(h.end.position)
.putShort(h.handler.position)
.putShort(h.type);
h = h.next;
}
}
attributeCount = 0;
if(localVar != null)
{
++attributeCount;
}
if(localVarType != null)
{
++attributeCount;
}
if(lineNumber != null)
{
++attributeCount;
}
7.18. METHODWRITER.JAVA 373
if(stackMap != null)
{
++attributeCount;
}
if(cattrs != null)
{
attributeCount += cattrs.getCount();
}
out.putShort(attributeCount);
if(localVar != null)
{
out.putShort(cw.newUTF8("LocalVariableTable"));
out.putInt(localVar.length + 2).putShort(localVarCount);
out.putByteArray(localVar.data, 0, localVar.length);
}
if(localVarType != null)
{
out.putShort(cw.newUTF8("LocalVariableTypeTable"));
out.putInt(localVarType.length + 2)
.putShort(localVarTypeCount);
out.putByteArray(localVarType.data, 0, localVarType.length);
}
if(lineNumber != null)
{
out.putShort(cw.newUTF8("LineNumberTable"));
out.putInt(lineNumber.length + 2).putShort(lineNumberCount);
out.putByteArray(lineNumber.data, 0, lineNumber.length);
}
if(stackMap != null)
{
boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap"));
out.putInt(stackMap.length + 2).putShort(frameCount);
out.putByteArray(stackMap.data, 0, stackMap.length);
}
if(cattrs != null)
{
cattrs.put(cw, code.data, code.length,
maxLocals, maxStack, out);
}
}
if(exceptionCount > 0)
{
out.putShort(cw.newUTF8("Exceptions"))
.putInt(2 * exceptionCount + 2);
out.putShort(exceptionCount);
for(int i = 0; i < exceptionCount; ++i)
{
out.putShort(exceptions[i]);
}
374 CHAPTER 7. JVM/CLOJURE/ASM/
}
if((access & Opcodes.ACC_SYNTHETIC) != 0
&& (cw.version & 0xffff) < Opcodes.V1_5)
{
out.putShort(cw.newUTF8("Synthetic")).putInt(0);
}
if((access & Opcodes.ACC_DEPRECATED) != 0)
{
out.putShort(cw.newUTF8("Deprecated")).putInt(0);
}
if(signature != null)
{
out.putShort(cw.newUTF8("Signature"))
.putInt(2)
.putShort(cw.newUTF8(signature));
}
if(annd != null)
{
out.putShort(cw.newUTF8("AnnotationDefault"));
out.putInt(annd.length);
out.putByteArray(annd.data, 0, annd.length);
}
if(anns != null)
{
out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
anns.put(out);
}
if(ianns != null)
{
out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
ianns.put(out);
}
if(panns != null)
{
out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations"));
AnnotationWriter.put(panns, out);
}
if(ipanns != null)
{
out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations"));
AnnotationWriter.put(ipanns, out);
}
if(attrs != null)
{
attrs.put(cw, null, 0, -1, -1, out);
}
}
// -------------------------------------------------------------------
// Utility methods: instruction resizing (used to handle GOTO_W and
7.18. METHODWRITER.JAVA 375
// JSR_W)
// ------------------------------------------------------------------
/**
* Resizes and replaces the temporary instructions inserted by
* {@link Label#resolve} for wide forward jumps, while keeping jump
* offsets and instruction addresses consistent. This may require to
* resize other existing instructions, or even to introduce new
* instructions: for example, increasing the size of an instruction
* by 2 at the middle of a method can increases the offset of an
* IFEQ instruction from 32766 to 32768, in which case IFEQ 32766
* must be replaced with IFNEQ 8 GOTO_W 32765. This, in turn, may
* require to increase the size of another jump instruction, and so
* on... All these operations are handled automatically by this
* method. <p> <i>This method must be called after all the method
* that is being built has been visited</i>. In particular, the
* {@link Label Label} objects used to construct the method are no
* longer valid after this method has been called.
*/
private void resizeInstructions(){
byte[] b = code.data; // bytecode of the method
int u, v, label; // indexes in b
int i, j; // loop indexes
/*
* 1st step: As explained above, resizing an instruction may require
* to resize another one, which may require to resize yet another
* one, and so on. The first step of the algorithm consists in
* finding all the instructions that need to be resized, without
* modifying the code. This is done by the following "fix point"
* algorithm:
*
* Parse the code to find the jump instructions whose offset will
* need more than 2 bytes to be stored (the future offset is
* computed from the current offset and from the number of bytes
* that will be inserted or removed between the source and target
* instructions). For each such instruction, adds an entry in (a
* copy of) the indexes and sizes arrays (if this has not already
* been done in a previous iteration!).
*
* If at least one entry has been added during the previous step, go
* back to the beginning, otherwise stop.
*
* In fact the real algorithm is complicated by the fact that the
* size of TABLESWITCH and LOOKUPSWITCH instructions depends on their
* position in the bytecode (because of padding). In order to ensure
* the convergence of the algorithm, the number of bytes to be added
* or removed from these instructions is over estimated during the
* previous loop, and computed exactly only after the loop is
* finished (this requires another pass to parse the bytecode of
* the method).
376 CHAPTER 7. JVM/CLOJURE/ASM/
*/
int[] allIndexes = new int[0]; // copy of indexes
int[] allSizes = new int[0]; // copy of sizes
boolean[] resize; // instructions to be resized
int newOffset; // future offset of a jump instruction
switch(ClassWriter.TYPE[opcode])
{
case ClassWriter.NOARG_INSN:
case ClassWriter.IMPLVAR_INSN:
u += 1;
break;
case ClassWriter.LABEL_INSN:
if(opcode > 201)
{
// converts temporary opcodes 202 to 217, 218 and
// 219 to IFEQ ... JSR (inclusive), IFNULL and
// IFNONNULL
opcode =
opcode < 218 ? opcode - 49 : opcode - 20;
label = u + readUnsignedShort(b, u + 1);
}
else
{
label = u + readShort(b, u + 1);
}
newOffset =
getNewOffset(allIndexes, allSizes, u, label);
if(newOffset < Short.MIN_VALUE
|| newOffset > Short.MAX_VALUE)
{
if(!resize[u])
{
if(opcode == Opcodes.GOTO
7.18. METHODWRITER.JAVA 377
|| opcode == Opcodes.JSR)
{
// two additional bytes will be required to
// replace this GOTO or JSR instruction with
// a GOTO_W or a JSR_W
insert = 2;
}
else
{
// five additional bytes will be required to
// replace this IFxxx <l> instruction with
// IFNOTxxx <l> GOTO_W <l>, where IFNOTxxx
// is the "opposite" opcode of IFxxx (i.e.,
// IFNE for IFEQ) and where <l> designates
// the instruction just after the GOTO_W.
insert = 5;
}
resize[u] = true;
}
}
u += 3;
break;
case ClassWriter.LABELW_INSN:
u += 5;
break;
case ClassWriter.TABL_INSN:
if(state == 1)
{
// true number of bytes to be added (or removed)
// from this instruction = (future number of
// padding bytes - current number of padding
// byte) - previously over estimated variation =
// = ((3 - newOffset%4) - (3 - u%4)) - u%4
// = (-newOffset%4 + u%4) - u%4
// = -(newOffset & 3)
newOffset =
getNewOffset(allIndexes, allSizes, 0, u);
insert = -(newOffset & 3);
}
else if(!resize[u])
{
// over estimation of the number of bytes to be
// added to this instruction = 3 - current number
// of padding bytes = 3 - (3 - u%4) = u%4 = u & 3
insert = u & 3;
resize[u] = true;
}
// skips instruction
u = u + 4 - (u & 3);
u += 4*(readInt(b,u+8) - readInt(b,u+4)+1)+12;
378 CHAPTER 7. JVM/CLOJURE/ASM/
break;
case ClassWriter.LOOK_INSN:
if(state == 1)
{
// like TABL_INSN
newOffset =
getNewOffset(allIndexes, allSizes, 0, u);
insert = -(newOffset & 3);
}
else if(!resize[u])
{
// like TABL_INSN
insert = u & 3;
resize[u] = true;
}
// skips instruction
u = u + 4 - (u & 3);
u += 8 * readInt(b, u + 4) + 8;
break;
case ClassWriter.WIDE_INSN:
opcode = b[u + 1] & 0xFF;
if(opcode == Opcodes.IINC)
{
u += 6;
}
else
{
u += 4;
}
break;
case ClassWriter.VAR_INSN:
case ClassWriter.SBYTE_INSN:
case ClassWriter.LDC_INSN:
u += 2;
break;
case ClassWriter.SHORT_INSN:
case ClassWriter.LDCW_INSN:
case ClassWriter.FIELDORMETH_INSN:
case ClassWriter.TYPE_INSN:
case ClassWriter.IINC_INSN:
u += 3;
break;
case ClassWriter.ITFMETH_INSN:
u += 5;
break;
// case ClassWriter.MANA_INSN:
default:
u += 4;
break;
}
7.18. METHODWRITER.JAVA 379
if(insert != 0)
{
// adds a new (u, insert) entry in the allIndexes and
// allSizes arrays
int[] newIndexes = new int[allIndexes.length + 1];
int[] newSizes = new int[allSizes.length + 1];
System.arraycopy(allIndexes,
0,
newIndexes,
0,
allIndexes.length);
System.arraycopy(allSizes,0,newSizes,0,allSizes.length);
newIndexes[allIndexes.length] = u;
newSizes[allSizes.length] = insert;
allIndexes = newIndexes;
allSizes = newSizes;
if(insert > 0)
{
state = 3;
}
}
}
if(state < 3)
{
--state;
}
} while(state != 0);
// 2nd step:
// copies the bytecode of the method into a new bytevector, updates
// the offsets, and inserts (or removes) bytes as requested.
u = 0;
while(u < code.length)
{
int opcode = b[u] & 0xFF;
switch(ClassWriter.TYPE[opcode])
{
case ClassWriter.NOARG_INSN:
case ClassWriter.IMPLVAR_INSN:
newCode.putByte(opcode);
u += 1;
break;
case ClassWriter.LABEL_INSN:
if(opcode > 201)
{
// changes temporary opcodes 202 to 217 (inclusive),
// 218 and 219 to IFEQ ... JSR (inclusive), IFNULL
380 CHAPTER 7. JVM/CLOJURE/ASM/
// and IFNONNULL
opcode = opcode < 218 ? opcode - 49 : opcode - 20;
label = u + readUnsignedShort(b, u + 1);
}
else
{
label = u + readShort(b, u + 1);
}
newOffset = getNewOffset(allIndexes, allSizes, u, label);
if(resize[u])
{
// replaces GOTO with GOTO_W, JSR with JSR_W and
// IFxxx <l> with IFNOTxxx <l> GOTO_W <l>, where
// IFNOTxxx is the "opposite" opcode of IFxxx
// (i.e., IFNE for IFEQ) and where <l> designates
// the instruction just after the GOTO_W.
if(opcode == Opcodes.GOTO)
{
newCode.putByte(200); // GOTO_W
}
else if(opcode == Opcodes.JSR)
{
newCode.putByte(201); // JSR_W
}
else
{
newCode.putByte(opcode <= 166
? ((opcode + 1) ^ 1) - 1
: opcode ^ 1);
newCode.putShort(8); // jump offset
newCode.putByte(200); // GOTO_W
// newOffset now computed from start of GOTO_W
newOffset -= 3;
}
newCode.putInt(newOffset);
}
else
{
newCode.putByte(opcode);
newCode.putShort(newOffset);
}
u += 3;
break;
case ClassWriter.LABELW_INSN:
label = u + readInt(b, u + 1);
newOffset = getNewOffset(allIndexes, allSizes, u, label);
newCode.putByte(opcode);
newCode.putInt(newOffset);
u += 5;
break;
7.18. METHODWRITER.JAVA 381
case ClassWriter.TABL_INSN:
// skips 0 to 3 padding bytes
v = u;
u = u + 4 - (v & 3);
// reads and copies instruction
newCode.putByte(Opcodes.TABLESWITCH);
newCode.length += (4 - newCode.length % 4) % 4;
label = v + readInt(b, u);
u += 4;
newOffset = getNewOffset(allIndexes, allSizes, v, label);
newCode.putInt(newOffset);
j = readInt(b, u);
u += 4;
newCode.putInt(j);
j = readInt(b, u) - j + 1;
u += 4;
newCode.putInt(readInt(b, u - 4));
for(; j > 0; --j)
{
label = v + readInt(b, u);
u += 4;
newOffset =
getNewOffset(allIndexes, allSizes, v, label);
newCode.putInt(newOffset);
}
break;
case ClassWriter.LOOK_INSN:
// skips 0 to 3 padding bytes
v = u;
u = u + 4 - (v & 3);
// reads and copies instruction
newCode.putByte(Opcodes.LOOKUPSWITCH);
newCode.length += (4 - newCode.length % 4) % 4;
label = v + readInt(b, u);
u += 4;
newOffset = getNewOffset(allIndexes, allSizes, v, label);
newCode.putInt(newOffset);
j = readInt(b, u);
u += 4;
newCode.putInt(j);
for(; j > 0; --j)
{
newCode.putInt(readInt(b, u));
u += 4;
label = v + readInt(b, u);
u += 4;
newOffset =
getNewOffset(allIndexes, allSizes, v, label);
newCode.putInt(newOffset);
}
382 CHAPTER 7. JVM/CLOJURE/ASM/
break;
case ClassWriter.WIDE_INSN:
opcode = b[u + 1] & 0xFF;
if(opcode == Opcodes.IINC)
{
newCode.putByteArray(b, u, 6);
u += 6;
}
else
{
newCode.putByteArray(b, u, 4);
u += 4;
}
break;
case ClassWriter.VAR_INSN:
case ClassWriter.SBYTE_INSN:
case ClassWriter.LDC_INSN:
newCode.putByteArray(b, u, 2);
u += 2;
break;
case ClassWriter.SHORT_INSN:
case ClassWriter.LDCW_INSN:
case ClassWriter.FIELDORMETH_INSN:
case ClassWriter.TYPE_INSN:
case ClassWriter.IINC_INSN:
newCode.putByteArray(b, u, 3);
u += 3;
break;
case ClassWriter.ITFMETH_INSN:
newCode.putByteArray(b, u, 5);
u += 5;
break;
// case MANA_INSN:
default:
newCode.putByteArray(b, u, 4);
u += 4;
break;
}
}
f.owner = labels;
Type[] args = Type.getArgumentTypes(descriptor);
f.initInputFrame(cw, access, args, maxLocals);
visitFrame(f);
Label l = labels;
while(l != null)
{
/*
* here we need the original label position. getNewOffset
* must therefore never have been called for this label.
*/
u = l.position - 3;
if((l.status & Label.STORE) != 0 ||
(u >= 0 && resize[u]))
{
getNewOffset(allIndexes, allSizes, l);
// TODO update offsets in UNINITIALIZED values
visitFrame(l.frame);
}
l = l.successor;
}
}
else
{
/*
* Resizing an existing stack map frame table is really
* hard. Not only the table must be parsed to update the
* offets, but new frames may be needed for jump
* instructions that were inserted by this method. And
* updating the offsets or inserting frames can change
* the format of the following frames, in case of packed
* frames. In practice the whole table must be recomputed.
* For this the frames are marked as potentially invalid.
* This will cause the whole class to be reread and
* rewritten with the COMPUTE_FRAMES option (see the
* ClassWriter.toByteArray method). This is not very
* efficient but is much easier and requires much less
* code than any other method I can think of.
*/
cw.invalidFrames = true;
}
}
// updates the exception handler block labels
Handler h = firstHandler;
while(h != null)
{
getNewOffset(allIndexes, allSizes, h.start);
getNewOffset(allIndexes, allSizes, h.end);
getNewOffset(allIndexes, allSizes, h.handler);
h = h.next;
384 CHAPTER 7. JVM/CLOJURE/ASM/
}
// updates the instructions addresses in the
// local var and line number tables
for(i = 0; i < 2; ++i)
{
ByteVector bv = i == 0 ? localVar : localVarType;
if(bv != null)
{
b = bv.data;
u = 0;
while(u < bv.length)
{
label = readUnsignedShort(b, u);
newOffset = getNewOffset(allIndexes, allSizes, 0, label);
writeShort(b, u, newOffset);
label += readUnsignedShort(b, u + 2);
newOffset = getNewOffset(allIndexes, allSizes, 0, label)
- newOffset;
writeShort(b, u + 2, newOffset);
u += 10;
}
}
}
if(lineNumber != null)
{
b = lineNumber.data;
u = 0;
while(u < lineNumber.length)
{
writeShort(b, u, getNewOffset(allIndexes,
allSizes,
0,
readUnsignedShort(b, u)));
u += 4;
}
}
// updates the labels of the other attributes
Attribute attr = cattrs;
while(attr != null)
{
Label[] labels = attr.getLabels();
if(labels != null)
{
for(i = labels.length - 1; i >= 0; --i)
{
getNewOffset(allIndexes, allSizes, labels[i]);
}
}
attr = attr.next;
}
7.18. METHODWRITER.JAVA 385
/**
* Reads an unsigned short value in the given byte array.
*
* @param b a byte array.
* @param index the start index of the value to be read.
* @return the read value.
*/
static int readUnsignedShort(final byte[] b, final int index){
return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
}
/**
* Reads a signed short value in the given byte array.
*
* @param b a byte array.
* @param index the start index of the value to be read.
* @return the read value.
*/
static short readShort(final byte[] b, final int index){
return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
}
/**
* Reads a signed int value in the given byte array.
*
* @param b a byte array.
* @param index the start index of the value to be read.
* @return the read value.
*/
static int readInt(final byte[] b, final int index){
return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
| ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
}
/**
* Writes a short value in the given byte array.
*
* @param b a byte array.
* @param index where the first byte of the short value must be written.
* @param s the value to be written in the given byte array.
*/
static void writeShort(final byte[] b, final int index, final int s){
b[index] = (byte) (s >>> 8);
b[index + 1] = (byte) s;
}
386 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Computes the future value of a bytecode offset. <p> Note: it is
* possible to have several entries for the same instruction in the
* <tt>indexes</tt> and <tt>sizes</tt>: two entries (index=a,size=b)
* and (index=a,size=b) are equivalent to a single entry
* (index=a,size=b+b).
*
* @param indexes current positions of the instructions to be resized.
* Each instruction must be designated by the index of
* its <i>last</i> byte, plus one (or, in other words, by
* the index of the <i>first</i> byte of the <i>next</i>
* instruction).
* @param sizes the number of bytes to be <i>added</i> to the above
* instructions. More precisely, for each
* i < <tt>len</tt>, <tt>sizes</tt>[i] bytes will be
* added at the end of the instruction designated by
* <tt>indexes</tt>[i] or, if <tt>sizes</tt>[i] is
* negative, the <i>last</i> |<tt>sizes[i]</tt>|
* bytes of the instruction will be removed (the
* instruction size <i>must not</i> become negative or
* null).
* @param begin index of the first byte of the source instruction.
* @param end index of the first byte of the target instruction.
* @return the future value of the given bytecode offset.
*/
static int getNewOffset(
final int[] indexes,
final int[] sizes,
final int begin,
final int end){
int offset = end - begin;
for(int i = 0; i < indexes.length; ++i)
{
if(begin < indexes[i] && indexes[i] <= end)
{
// forward jump
offset += sizes[i];
}
else if(end < indexes[i] && indexes[i] <= begin)
{
// backward jump
offset -= sizes[i];
}
}
return offset;
}
/**
* Updates the offset of the given label.
7.19. OPCODES.JAVA 387
*
* @param indexes current positions of the instructions to be resized.
* Each instruction must be designated by the index of
* its <i>last</i> byte, plus one (or, in other words,
* by the index of the <i>first</i> byte of the
* <i>next</i> instruction).
* @param sizes the number of bytes to be <i>added</i> to the above
* instructions. More precisely, for each
* i < <tt>len</tt>, <tt>sizes</tt>[i] bytes will be
* added at the end of the instruction designated by
* <tt>indexes</tt>[i] or, if <tt>sizes</tt>[i] is
* negative, the <i>last</i> | <tt>sizes[i]</tt>|
* bytes of the instruction will be removed (the
* instruction size <i>must not</i> become negative
* or null).
* @param label the label whose offset must be updated.
*/
static void getNewOffset(
final int[] indexes,
final int[] sizes,
final Label label){
if((label.status & Label.RESIZED) == 0)
{
label.position = getNewOffset(indexes, sizes, 0, label.position);
label.status |= Label.RESIZED;
}
}
}
7.19 Opcodes.java
Opcodes.java
/**
* Defines the JVM opcodes, access flags and array type codes. This
* interface does not define all the JVM opcodes because some opcodes
* are automatically handled. For example, the xLOAD and xSTORE opcodes
* are automatically replaced by xLOAD_n and xSTORE_n opcodes when
* possible. The xLOAD_n and xSTORE_n opcodes are therefore not
* defined in this interface. Likewise for LDC, automatically replaced
* by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and JSR_W.
*
388 CHAPTER 7. JVM/CLOJURE/ASM/
// versions
// access flags
int T_BOOLEAN = 4;
int T_CHAR = 5;
int T_FLOAT = 6;
int T_DOUBLE = 7;
int T_BYTE = 8;
int T_SHORT = 9;
int T_INT = 10;
int T_LONG = 11;
7.19. OPCODES.JAVA 389
/**
* Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}.
*/
int F_NEW = -1;
/**
* Represents a compressed frame with complete frame data.
*/
int F_FULL = 0;
/**
* Represents a compressed frame where locals are the same as the
* locals in the previous frame, except that additional 1-3 locals
* are defined, and with an empty stack.
*/
int F_APPEND = 1;
/**
* Represents a compressed frame where locals are the same as the
* locals in the previous frame, except that the last 1-3 locals are
* absent and with an empty stack.
*/
int F_CHOP = 2;
/**
* Represents a compressed frame with exactly the same locals as the
* previous frame and with an empty stack.
*/
int F_SAME = 3;
/**
* Represents a compressed frame with exactly the same locals as the
* previous frame and with a single value on the stack.
*/
int F_SAME1 = 4;
int ICONST_M1 = 2; // -
int ICONST_0 = 3; // -
int ICONST_1 = 4; // -
int ICONST_2 = 5; // -
int ICONST_3 = 6; // -
int ICONST_4 = 7; // -
int ICONST_5 = 8; // -
int LCONST_0 = 9; // -
int LCONST_1 = 10; // -
int FCONST_0 = 11; // -
int FCONST_1 = 12; // -
int FCONST_2 = 13; // -
int DCONST_0 = 14; // -
int DCONST_1 = 15; // -
int BIPUSH = 16; // visitIntInsn
int SIPUSH = 17; // -
int LDC = 18; // visitLdcInsn
// int LDC_W = 19; // -
// int LDC2_W = 20; // -
int ILOAD = 21; // visitVarInsn
int LLOAD = 22; // -
int FLOAD = 23; // -
int DLOAD = 24; // -
int ALOAD = 25; // -
// int ILOAD_0 = 26; // -
// int ILOAD_1 = 27; // -
// int ILOAD_2 = 28; // -
// int ILOAD_3 = 29; // -
// int LLOAD_0 = 30; // -
// int LLOAD_1 = 31; // -
// int LLOAD_2 = 32; // -
// int LLOAD_3 = 33; // -
// int FLOAD_0 = 34; // -
// int FLOAD_1 = 35; // -
// int FLOAD_2 = 36; // -
// int FLOAD_3 = 37; // -
// int DLOAD_0 = 38; // -
// int DLOAD_1 = 39; // -
// int DLOAD_2 = 40; // -
// int DLOAD_3 = 41; // -
// int ALOAD_0 = 42; // -
// int ALOAD_1 = 43; // -
// int ALOAD_2 = 44; // -
// int ALOAD_3 = 45; // -
int IALOAD = 46; // visitInsn
int LALOAD = 47; // -
int FALOAD = 48; // -
int DALOAD = 49; // -
int AALOAD = 50; // -
int BALOAD = 51; // -
7.19. OPCODES.JAVA 391
7.20 Type.java
Type.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* A Java type. This class can be used to make it easier to manipulate
* type and method descriptors.
*
* @author Eric Bruneton
* @author Chris Nokleberg
*/
public class Type{
/**
* The sort of the <tt>void</tt> type. See {@link #getSort getSort}.
*/
public final static int VOID = 0;
/**
* The sort of the <tt>boolean</tt> type. See {@link #getSort getSort}.
*/
public final static int BOOLEAN = 1;
/**
* The sort of the <tt>char</tt> type. See {@link #getSort getSort}.
*/
public final static int CHAR = 2;
/**
* The sort of the <tt>byte</tt> type. See {@link #getSort getSort}.
*/
public final static int BYTE = 3;
/**
* The sort of the <tt>short</tt> type. See {@link #getSort getSort}.
*/
public final static int SHORT = 4;
7.20. TYPE.JAVA 395
/**
* The sort of the <tt>int</tt> type. See {@link #getSort getSort}.
*/
public final static int INT = 5;
/**
* The sort of the <tt>float</tt> type. See {@link #getSort getSort}.
*/
public final static int FLOAT = 6;
/**
* The sort of the <tt>long</tt> type. See {@link #getSort getSort}.
*/
public final static int LONG = 7;
/**
* The sort of the <tt>double</tt> type. See {@link #getSort getSort}.
*/
public final static int DOUBLE = 8;
/**
* The sort of array reference types. See {@link #getSort getSort}.
*/
public final static int ARRAY = 9;
/**
* The sort of object reference type. See {@link #getSort getSort}.
*/
public final static int OBJECT = 10;
/**
* The <tt>void</tt> type.
*/
public final static Type VOID_TYPE = new Type(VOID);
/**
* The <tt>boolean</tt> type.
*/
public final static Type BOOLEAN_TYPE = new Type(BOOLEAN);
/**
* The <tt>char</tt> type.
*/
public final static Type CHAR_TYPE = new Type(CHAR);
/**
* The <tt>byte</tt> type.
*/
public final static Type BYTE_TYPE = new Type(BYTE);
396 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* The <tt>short</tt> type.
*/
public final static Type SHORT_TYPE = new Type(SHORT);
/**
* The <tt>int</tt> type.
*/
public final static Type INT_TYPE = new Type(INT);
/**
* The <tt>float</tt> type.
*/
public final static Type FLOAT_TYPE = new Type(FLOAT);
/**
* The <tt>long</tt> type.
*/
public final static Type LONG_TYPE = new Type(LONG);
/**
* The <tt>double</tt> type.
*/
public final static Type DOUBLE_TYPE = new Type(DOUBLE);
// -------------------------------------------------------------------
// Fields
// -------------------------------------------------------------------
/**
* The sort of this Java type.
*/
private final int sort;
/**
* A buffer containing the descriptor of this Java type. This field
* is only used for reference types.
*/
private char[] buf;
/**
* The offset of the descriptor of this Java type in {@link #buf buf}.
* This field is only used for reference types.
*/
private int off;
/**
* The length of the descriptor of this Java type.
*/
7.20. TYPE.JAVA 397
// -------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------
/**
* Constructs a primitive type.
*
* @param sort the sort of the primitive type to be constructed.
*/
private Type(final int sort){
this.sort = sort;
this.len = 1;
}
/**
* Constructs a reference type.
*
* @param sort the sort of the reference type to be constructed.
* @param buf a buffer containing the descriptor of the previous type.
* @param off the offset of this descriptor in the previous buffer.
* @param len the length of this descriptor.
*/
private Type(final int sort,
final char[] buf,
final int off,
final int len){
this.sort = sort;
this.buf = buf;
this.off = off;
this.len = len;
}
/**
* Returns the Java type corresponding to the given type descriptor.
*
* @param typeDescriptor a type descriptor.
* @return the Java type corresponding to the given type descriptor.
*/
public static Type getType(final String typeDescriptor){
return getType(typeDescriptor.toCharArray(), 0);
}
/**
* Returns the Java type corresponding to the given class.
*
* @param c a class.
* @return the Java type corresponding to the given class.
*/
398 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Returns the {@link Type#OBJECT} type for the given internal class
* name. This is a shortcut method for
* <code>Type.getType("L"+name+";")</code>.
7.20. TYPE.JAVA 399
/**
* Returns the Java types corresponding to the argument types of the
* given method descriptor.
*
* @param methodDescriptor a method descriptor.
* @return the Java types corresponding to the argument types of the
* given method descriptor.
*/
public static Type[] getArgumentTypes(final String methodDescriptor){
char[] buf = methodDescriptor.toCharArray();
int off = 1;
int size = 0;
while(true)
{
char car = buf[off++];
if(car == ))
{
break;
}
else if(car == L)
{
while(buf[off++] != ;)
{
}
++size;
}
else if(car != [)
{
++size;
}
}
Type[] args = new Type[size];
off = 1;
size = 0;
while(buf[off] != ))
400 CHAPTER 7. JVM/CLOJURE/ASM/
{
args[size] = getType(buf, off);
off += args[size].len;
size += 1;
}
return args;
}
/**
* Returns the Java types corresponding to the argument types of the
* given method.
*
* @param method a method.
* @return the Java types corresponding to the argument types of the
* given method.
*/
public static Type[] getArgumentTypes(final Method method){
Class[] classes = method.getParameterTypes();
Type[] types = new Type[classes.length];
for(int i = classes.length - 1; i >= 0; --i)
{
types[i] = getType(classes[i]);
}
return types;
}
/**
* Returns the Java type corresponding to the return type of the given
* method descriptor.
*
* @param methodDescriptor a method descriptor.
* @return the Java type corresponding to the return type of the given
* method descriptor.
*/
public static Type getReturnType(final String methodDescriptor){
char[] buf = methodDescriptor.toCharArray();
return getType(buf, methodDescriptor.indexOf()) + 1);
}
/**
* Returns the Java type corresponding to the return type of the given
* method.
*
* @param method a method.
* @return the Java type corresponding to the return type of the given
* method.
*/
public static Type getReturnType(final Method method){
return getType(method.getReturnType());
}
7.20. TYPE.JAVA 401
/**
* Returns the Java type corresponding to the given type descriptor.
*
* @param buf a buffer containing a type descriptor.
* @param off the offset of this descriptor in the previous buffer.
* @return the Java type corresponding to the given type descriptor.
*/
private static Type getType(final char[] buf, final int off){
int len;
switch(buf[off])
{
caseV:
return VOID_TYPE;
caseZ:
return BOOLEAN_TYPE;
caseC:
return CHAR_TYPE;
caseB:
return BYTE_TYPE;
caseS:
return SHORT_TYPE;
caseI:
return INT_TYPE;
caseF:
return FLOAT_TYPE;
caseJ:
return LONG_TYPE;
caseD:
return DOUBLE_TYPE;
case[:
len = 1;
while(buf[off + len] == [)
{
++len;
}
if(buf[off + len] == L)
{
++len;
while(buf[off + len] != ;)
{
++len;
}
}
return new Type(ARRAY, buf, off, len + 1);
// case L:
default:
len = 1;
while(buf[off + len] != ;)
{
402 CHAPTER 7. JVM/CLOJURE/ASM/
++len;
}
return new Type(OBJECT, buf, off, len + 1);
}
}
// -------------------------------------------------------------------
// Accessors
// -------------------------------------------------------------------
/**
* Returns the sort of this Java type.
*
* @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN},
* {@link #CHAR CHAR}, {@link #BYTE BYTE}, {@link #SHORT SHORT},
* {@link #INT INT}, {@link #FLOAT FLOAT}, {@link #LONG LONG},
* {@link #DOUBLE DOUBLE}, {@link #ARRAY ARRAY} or
* {@link #OBJECT OBJECT}.
*/
public int getSort(){
return sort;
}
/**
* Returns the number of dimensions of this array type. This method
* should only be used for an array type.
*
* @return the number of dimensions of this array type.
*/
public int getDimensions(){
int i = 1;
while(buf[off + i] == [)
{
++i;
}
return i;
}
/**
* Returns the type of the elements of this array type. This method
* should only be used for an array type.
*
* @return Returns the type of the elements of this array type.
*/
public Type getElementType(){
return getType(buf, off + getDimensions());
}
/**
* Returns the name of the class corresponding to this type.
7.20. TYPE.JAVA 403
*
* @return the fully qualified name of the class corresponding to
* this type.
*/
public String getClassName(){
switch(sort)
{
case VOID:
return "void";
case BOOLEAN:
return "boolean";
case CHAR:
return "char";
case BYTE:
return "byte";
case SHORT:
return "short";
case INT:
return "int";
case FLOAT:
return "float";
case LONG:
return "long";
case DOUBLE:
return "double";
case ARRAY:
StringBuffer b =
new StringBuffer(getElementType().getClassName());
for(int i = getDimensions(); i > 0; --i)
{
b.append("[]");
}
return b.toString();
// case OBJECT:
default:
return new String(buf, off + 1, len - 2).replace(/, .);
}
}
/**
* Returns the internal name of the class corresponding to this object
* type. The internal name of a class is its fully qualified name,
* where . are replaced by /. This method should only be used for
* an object type.
*
* @return the internal name of the class corresponding to this object
* type.
*/
public String getInternalName(){
return new String(buf, off + 1, len - 2);
404 CHAPTER 7. JVM/CLOJURE/ASM/
// -------------------------------------------------------------------
// Conversion to type descriptors
// -------------------------------------------------------------------
/**
* Returns the descriptor corresponding to this Java type.
*
* @return the descriptor corresponding to this Java type.
*/
public String getDescriptor(){
StringBuffer buf = new StringBuffer();
getDescriptor(buf);
return buf.toString();
}
/**
* Returns the descriptor corresponding to the given argument and return
* types.
*
* @param returnType the return type of the method.
* @param argumentTypes the argument types of the method.
* @return the descriptor corresponding to the given argument and return
* types.
*/
public static String getMethodDescriptor(
final Type returnType,
final Type[] argumentTypes){
StringBuffer buf = new StringBuffer();
buf.append(();
for(int i = 0; i < argumentTypes.length; ++i)
{
argumentTypes[i].getDescriptor(buf);
}
buf.append());
returnType.getDescriptor(buf);
return buf.toString();
}
/**
* Appends the descriptor corresponding to this Java type to the given
* string buffer.
*
* @param buf the string buffer to which the descriptor must be appended.
*/
private void getDescriptor(final StringBuffer buf){
switch(sort)
{
case VOID:
7.20. TYPE.JAVA 405
buf.append(V);
return;
case BOOLEAN:
buf.append(Z);
return;
case CHAR:
buf.append(C);
return;
case BYTE:
buf.append(B);
return;
case SHORT:
buf.append(S);
return;
case INT:
buf.append(I);
return;
case FLOAT:
buf.append(F);
return;
case LONG:
buf.append(J);
return;
case DOUBLE:
buf.append(D);
return;
// case ARRAY:
// case OBJECT:
default:
buf.append(this.buf, off, len);
}
}
// -------------------------------------------------------------------
// Direct conversion from classes to type descriptors,
// without intermediate Type objects
// -------------------------------------------------------------------
/**
* Returns the internal name of the given class. The internal name of a
* class is its fully qualified name, where . are replaced by /.
*
* @param c an object class.
* @return the internal name of the given class.
*/
public static String getInternalName(final Class c){
return c.getName().replace(., /);
}
/**
406 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Returns the descriptor corresponding to the given constructor.
*
* @param c a {@link Constructor Constructor} object.
* @return the descriptor of the given constructor.
*/
public static String getConstructorDescriptor(final Constructor c){
Class[] parameters = c.getParameterTypes();
StringBuffer buf = new StringBuffer();
buf.append(();
for(int i = 0; i < parameters.length; ++i)
{
getDescriptor(buf, parameters[i]);
}
return buf.append(")V").toString();
}
/**
* Returns the descriptor corresponding to the given method.
*
* @param m a {@link Method Method} object.
* @return the descriptor of the given method.
*/
public static String getMethodDescriptor(final Method m){
Class[] parameters = m.getParameterTypes();
StringBuffer buf = new StringBuffer();
buf.append(();
for(int i = 0; i < parameters.length; ++i)
{
getDescriptor(buf, parameters[i]);
}
buf.append());
getDescriptor(buf, m.getReturnType());
return buf.toString();
}
/**
* Appends the descriptor of the given class to the given string buffer.
*
7.20. TYPE.JAVA 407
* @param buf the string buffer to which the descriptor must be appended.
* @param c the class whose descriptor must be computed.
*/
private static void getDescriptor(final StringBuffer buf, final Class c){
Class d = c;
while(true)
{
if(d.isPrimitive())
{
char car;
if(d == Integer.TYPE)
{
car = I;
}
else if(d == Void.TYPE)
{
car = V;
}
else if(d == Boolean.TYPE)
{
car = Z;
}
else if(d == Byte.TYPE)
{
car = B;
}
else if(d == Character.TYPE)
{
car = C;
}
else if(d == Short.TYPE)
{
car = S;
}
else if(d == Double.TYPE)
{
car = D;
}
else if(d == Float.TYPE)
{
car = F;
}
else /* if (d == Long.TYPE) */
{
car = J;
}
buf.append(car);
return;
}
else if(d.isArray())
408 CHAPTER 7. JVM/CLOJURE/ASM/
{
buf.append([);
d = d.getComponentType();
}
else
{
buf.append(L);
String name = d.getName();
int len = name.length();
for(int i = 0; i < len; ++i)
{
char car = name.charAt(i);
buf.append(car == . ? / : car);
}
buf.append(;);
return;
}
}
}
// -------------------------------------------------------------------
// Corresponding size and opcodes
// -------------------------------------------------------------------
/**
* Returns the size of values of this type.
*
* @return the size of values of this type, i.e., 2 for <tt>long</tt> and
* <tt>double</tt>, and 1 otherwise.
*/
public int getSize(){
return sort == LONG || sort == DOUBLE ? 2 : 1;
}
/**
* Returns a JVM instruction opcode adapted to this Java type.
*
* @param opcode a JVM instruction opcode. This opcode must be one of
* ILOAD, ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL,
* IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR,
* IXOR and IRETURN.
* @return an opcode that is similar to the given opcode, but adapted to
* this Java type. For example, if this type is <tt>float</tt>
* and <tt>opcode</tt> is IRETURN, this method returns FRETURN.
*/
public int getOpcode(final int opcode){
if(opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE)
{
switch(sort)
{
7.20. TYPE.JAVA 409
case BOOLEAN:
case BYTE:
return opcode + 5;
case CHAR:
return opcode + 6;
case SHORT:
return opcode + 7;
case INT:
return opcode;
case FLOAT:
return opcode + 2;
case LONG:
return opcode + 1;
case DOUBLE:
return opcode + 3;
// case ARRAY:
// case OBJECT:
default:
return opcode + 4;
}
}
else
{
switch(sort)
{
case VOID:
return opcode + 5;
case BOOLEAN:
case CHAR:
case BYTE:
case SHORT:
case INT:
return opcode;
case FLOAT:
return opcode + 2;
case LONG:
return opcode + 1;
case DOUBLE:
return opcode + 3;
// case ARRAY:
// case OBJECT:
default:
return opcode + 4;
}
}
}
// -------------------------------------------------------------------
// Equals, hashCode and toString
// -------------------------------------------------------------------
410 CHAPTER 7. JVM/CLOJURE/ASM/
/**
* Tests if the given object is equal to this type.
*
* @param o the object to be compared to this type.
* @return <tt>true</tt> if the given object is equal to this type.
*/
public boolean equals(final Object o){
if(this == o)
{
return true;
}
if(!(o instanceof Type))
{
return false;
}
Type t = (Type) o;
if(sort != t.sort)
{
return false;
}
if(sort == Type.OBJECT || sort == Type.ARRAY)
{
if(len != t.len)
{
return false;
}
for(int i = off, j = t.off, end = i + len; i < end; i++, j++)
{
if(buf[i] != t.buf[j])
{
return false;
}
}
}
return true;
}
/**
* Returns a hash code value for this type.
*
* @return a hash code value for this type.
*/
public int hashCode(){
int hc = 13 * sort;
if(sort == Type.OBJECT || sort == Type.ARRAY)
{
for(int i = off, end = i + len; i < end; i++)
{
hc = 17 * (hc + buf[i]);
7.20. TYPE.JAVA 411
}
}
return hc;
}
/**
* Returns a string representation of this type.
*
* @return the descriptor of this type.
*/
public String toString(){
return getDescriptor();
}
}
-
412 CHAPTER 7. JVM/CLOJURE/ASM/
Chapter 8
jvm/clojure/asm/commons
8.1 AdviceAdapter.java
(Opcodes [387]) (GeneratorAdapter [453])
AdviceAdapter.java
\getchunk{France Telecom Copyright}
package clojure.asm.commons;
import java.util.ArrayList;
import java.util.HashMap;
import clojure.asm.Label;
import clojure.asm.MethodVisitor;
import clojure.asm.Opcodes;
import clojure.asm.Type;
/**
* A {@link clojure.asm.MethodAdapter} to insert before, after and around
* advices in methods and constructors. <p> The behavior for constructors
* is like this: <ol>
* <p/>
* <li>as long as the INVOKESPECIAL for the object initialization has
* not been reached, every bytecode instruction is dispatched in the
* ctor code visitor</li>
* <p/>
* <li>when this one is reached, it is only added in the ctor code
* visitor and a JP invoke is added</li>
* <p/>
* <li>after that, only the other code visitor receives the
* instructions</li>
* <p/>
* </ol>
413
414 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
*
* @author Eugene Kuleshov
* @author Eric Bruneton
*/
public abstract class AdviceAdapter
extends GeneratorAdapter implements Opcodes{
private static final Object THIS = new Object();
private static final Object OTHER = new Object();
/**
* Creates a new {@link AdviceAdapter}.
*
* @param mv the method visitor to which this adapter delegates
* calls.
* @param access the methods access flags (see {@link Opcodes}).
* @param name the methods name.
* @param desc the methods descriptor (see {@link Type Type}).
*/
public AdviceAdapter(
final MethodVisitor mv,
final int access,
final String name,
final String desc){
super(mv, access, name, desc);
methodAccess = access;
methodDesc = desc;
constructor = "<init>".equals(name);
}
case NOP:
case LALOAD: // remove 2 add 2
case DALOAD: // remove 2 add 2
case LNEG:
case DNEG:
case FNEG:
case INEG:
case L2D:
case D2L:
416 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
case F2I:
case I2B:
case I2C:
case I2S:
case I2F:
case Opcodes.ARRAYLENGTH:
break;
case ACONST_NULL:
case ICONST_M1:
case ICONST_0:
case ICONST_1:
case ICONST_2:
case ICONST_3:
case ICONST_4:
case ICONST_5:
case FCONST_0:
case FCONST_1:
case FCONST_2:
case F2L: // 1 before 2 after
case F2D:
case I2L:
case I2D:
pushValue(OTHER);
break;
case LCONST_0:
case LCONST_1:
case DCONST_0:
case DCONST_1:
pushValue(OTHER);
pushValue(OTHER);
break;
case POP2:
case LSUB:
case LMUL:
case LDIV:
case LREM:
case LADD:
case LAND:
case LOR:
case LXOR:
case DADD:
case DMUL:
case DSUB:
case DDIV:
case DREM:
popValue();
popValue();
break;
case IASTORE:
case FASTORE:
case AASTORE:
case BASTORE:
case CASTORE:
case SASTORE:
case LCMP: // 4 before 1 after
case DCMPL:
case DCMPG:
popValue();
popValue();
418 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
popValue();
break;
case LASTORE:
case DASTORE:
popValue();
popValue();
popValue();
popValue();
break;
case DUP:
pushValue(peekValue());
break;
case DUP_X1:
// TODO optimize this
{
Object o1 = popValue();
Object o2 = popValue();
pushValue(o1);
pushValue(o2);
pushValue(o1);
}
break;
case DUP_X2:
// TODO optimize this
{
Object o1 = popValue();
Object o2 = popValue();
Object o3 = popValue();
pushValue(o1);
pushValue(o3);
pushValue(o2);
pushValue(o1);
}
break;
case DUP2:
// TODO optimize this
{
Object o1 = popValue();
Object o2 = popValue();
pushValue(o2);
pushValue(o1);
pushValue(o2);
pushValue(o1);
}
break;
8.1. ADVICEADAPTER.JAVA 419
case DUP2_X1:
// TODO optimize this
{
Object o1 = popValue();
Object o2 = popValue();
Object o3 = popValue();
pushValue(o2);
pushValue(o1);
pushValue(o3);
pushValue(o2);
pushValue(o1);
}
break;
case DUP2_X2:
// TODO optimize this
{
Object o1 = popValue();
Object o2 = popValue();
Object o3 = popValue();
Object o4 = popValue();
pushValue(o2);
pushValue(o1);
pushValue(o4);
pushValue(o3);
pushValue(o2);
pushValue(o1);
}
break;
case SWAP:
{
Object o1 = popValue();
Object o2 = popValue();
pushValue(o1);
pushValue(o2);
}
break;
}
}
else
{
switch(opcode)
{
case RETURN:
case IRETURN:
case FRETURN:
case ARETURN:
case LRETURN:
420 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
case DRETURN:
case ATHROW:
onMethodExit(opcode);
break;
}
}
mv.visitInsn(opcode);
}
if(constructor)
{
switch(opcode)
{
case ILOAD:
case FLOAD:
pushValue(OTHER);
break;
case LLOAD:
case DLOAD:
pushValue(OTHER);
pushValue(OTHER);
break;
case ALOAD:
pushValue(var == 0 ? THIS : OTHER);
break;
case ASTORE:
case ISTORE:
case FSTORE:
popValue();
break;
case LSTORE:
case DSTORE:
popValue();
popValue();
break;
}
}
}
if(constructor)
8.1. ADVICEADAPTER.JAVA 421
{
char c = desc.charAt(0);
boolean longOrDouble = c == J || c == D;
switch(opcode)
{
case GETSTATIC:
pushValue(OTHER);
if(longOrDouble)
{
pushValue(OTHER);
}
break;
case PUTSTATIC:
popValue();
if(longOrDouble)
{
popValue();
}
break;
case PUTFIELD:
popValue();
if(longOrDouble)
{
popValue();
popValue();
}
break;
// case GETFIELD:
default:
if(longOrDouble)
{
pushValue(OTHER);
}
}
}
}
if(constructor)
422 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
{
pushValue(OTHER);
if(cst instanceof Double || cst instanceof Long)
{
pushValue(OTHER);
}
}
}
if(constructor)
{
for(int i = 0; i < dims; i++)
{
popValue();
}
pushValue(OTHER);
}
}
if(constructor)
{
Type[] types = Type.getArgumentTypes(desc);
for(int i = 0; i < types.length; i++)
{
popValue();
if(types[i].getSize() == 2)
{
popValue();
}
}
8.1. ADVICEADAPTER.JAVA 423
switch(opcode)
{
// case INVOKESTATIC:
// break;
case INVOKEINTERFACE:
case INVOKEVIRTUAL:
popValue(); // objectref
break;
case INVOKESPECIAL:
Object type = popValue(); // objectref
if(type == THIS && !superInitialized)
{
onMethodEnter();
superInitialized = true;
// once super has been initialized it is no longer
// necessary to keep track of stack state
constructor = false;
}
break;
}
if(constructor)
{
switch(opcode)
{
case IFEQ:
case IFNE:
case IFLT:
case IFGE:
case IFGT:
case IFLE:
case IFNULL:
case IFNONNULL:
424 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
popValue();
break;
case IF_ICMPEQ:
case IF_ICMPNE:
case IF_ICMPLT:
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ACMPEQ:
case IF_ACMPNE:
popValue();
popValue();
break;
case JSR:
pushValue(OTHER);
break;
}
addBranch(label);
}
}
if(constructor)
{
popValue();
addBranches(dflt, labels);
}
}
if(constructor)
{
popValue();
addBranches(dflt, labels);
}
}
8.1. ADVICEADAPTER.JAVA 425
/**
* Called at the beginning of the method or after super class class
* call in the constructor. <br><br>
* <p/>
* <i>Custom code can use or change all the local variables, but
* should not change state of the stack.</i>
*/
protected abstract void onMethodEnter();
/**
* Called before explicit exit from the method using either return
* or throw. Top element on the stack contains the return value or
* exception instance.
* For example:
* <p/>
* <pre>
* public void onMethodExit(int opcode) {
* if(opcode==RETURN) {
* visitInsn(ACONST_NULL);
* } else if(opcode==ARETURN || opcode==ATHROW) {
426 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
* dup();
* } else {
* if(opcode==LRETURN || opcode==DRETURN) {
* dup2();
* } else {
* dup();
* }
* box(Type.getReturnType(this.methodDesc));
* }
* visitIntInsn(SIPUSH, opcode);
* visitMethodInsn(INVOKESTATIC, owner, "onExit",
"(Ljava/lang/Object;I)V");
* }
* <p/>
* // an actual call back method
* public static void onExit(int opcode, Object param) {
* ...
* </pre>
* <p/>
* <br><br>
* <p/>
* <i>Custom code can use or change all the local variables, but should
* not change state of the stack.</i>
*
* @param opcode one of the RETURN, IRETURN, FRETURN, ARETURN, LRETURN,
* DRETURN or ATHROW
*/
protected abstract void onMethodExit(int opcode);
8.2 AnalyzerAdapter.java
(MethodAdapter [314])
AnalyzerAdapter.java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
8.2. ANALYZERADAPTER.JAVA 427
import clojure.asm.Label;
import clojure.asm.MethodAdapter;
import clojure.asm.MethodVisitor;
import clojure.asm.Opcodes;
import clojure.asm.Type;
/**
* A {@link MethodAdapter} that keeps track of stack map frame changes
* between
* {@link #visitFrame(int,int,Object[],int,Object[]) visitFrame} calls.
* This adapter must be used with the
* {@link clojure.asm.ClassReader#EXPAND_FRAMES} option. Each
* visit<i>XXX</i> instruction delegates to the next visitor in the
* chain, if any, and then simulates the effect of this instruction
* on the stack map frame, represented by {@link #locals} and
* {@link #stack}. The next visitor in the chain can get the state
* of the stack map frame <i>before</i> each instruction by reading
* the value of these fields in its visit<i>XXX</i> methods (this
* requires a reference to the AnalyzerAdapter that is before it
* in the chain).
*
* @author Eric Bruneton
*/
public class AnalyzerAdapter extends MethodAdapter{
/**
* <code>List</code> of the local variable slots for current execution
* frame. Primitive types are represented by {@link Opcodes#TOP},
* {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG},
* {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or
* {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented
* by a two elements, the second one being TOP). Reference types are
* represented by String objects (representing internal names, or type
* descriptors for array types), and uninitialized types by Label
* objects (this label designates the NEW instruction that created
* this uninitialized value). This field is <tt>null</tt> for
* unreacheable instructions.
*/
public List locals;
/**
* <code>List</code> of the operand stack slots for current execution
* frame. Primitive types are represented by {@link Opcodes#TOP},
* {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG},
* {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or
* {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented
* by a two elements, the second one being TOP). Reference types are
* represented by String objects (representing internal names, or type
* descriptors for array types), and uninitialized types by Label
* objects (this label designates the NEW instruction that created this
428 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* The labels that designate the next instruction to be visited. May be
* <tt>null</tt>.
*/
private List labels;
/**
* Information about uninitialized types in the current execution frame.
* This map associates internal names to Label objects. Each label
* designates a NEW instruction that created the currently uninitialized
* types, and the associated internal name represents the NEW operand,
* i.e. the final, initialized type value.
*/
private Map uninitializedTypes;
/**
* The maximum stack size of this method.
*/
private int maxStack;
/**
* The maximum number of local variables of this method.
*/
private int maxLocals;
/**
* Creates a new {@link AnalyzerAdapter}.
*
* @param owner the owners class name.
* @param access the methods access flags (see {@link Opcodes}).
* @param name the methods name.
* @param desc the methods descriptor (see {@link Type Type}).
* @param mv the method visitor to which this adapter delegates
* calls. May be <tt>null</tt>.
*/
public AnalyzerAdapter(
final String owner,
final int access,
final String name,
final String desc,
final MethodVisitor mv){
super(mv);
locals = new ArrayList();
stack = new ArrayList();
uninitializedTypes = new HashMap();
8.2. ANALYZERADAPTER.JAVA 429
if(mv != null)
{
mv.visitFrame(type, nLocal, local, nStack, stack);
}
if(this.locals != null)
{
this.locals.clear();
this.stack.clear();
}
else
{
this.locals = new ArrayList();
this.stack = new ArrayList();
}
visitFrameTypes(nLocal, local, this.locals);
visitFrameTypes(nStack, stack, this.stack);
maxStack = Math.max(maxStack, this.stack.size());
}
|| opcode == Opcodes.ATHROW)
{
this.locals = null;
this.stack = null;
}
}
pushDesc(desc);
labels = null;
}
push(Opcodes.TOP);
}
else if(cst instanceof String)
{
push("java/lang/String");
}
else if(cst instanceof Type)
{
push("java/lang/Class");
}
else
{
throw new IllegalArgumentException();
}
labels = null;
}
// -------------------------------------------------------------------
caseI:
push(Opcodes.INTEGER);
return;
caseF:
push(Opcodes.FLOAT);
return;
caseJ:
push(Opcodes.LONG);
push(Opcodes.TOP);
return;
caseD:
push(Opcodes.DOUBLE);
push(Opcodes.TOP);
return;
case[:
if(index == 0)
{
push(desc);
}
else
{
push(desc.substring(index, desc.length()));
}
break;
// case L:
default:
if(index == 0)
{
push(desc.substring(1, desc.length() - 1));
}
else
{
push(desc.substring(index + 1, desc.length() - 1));
}
return;
}
}
case Opcodes.ICONST_2:
case Opcodes.ICONST_3:
case Opcodes.ICONST_4:
case Opcodes.ICONST_5:
case Opcodes.BIPUSH:
case Opcodes.SIPUSH:
push(Opcodes.INTEGER);
break;
case Opcodes.LCONST_0:
case Opcodes.LCONST_1:
push(Opcodes.LONG);
push(Opcodes.TOP);
break;
case Opcodes.FCONST_0:
case Opcodes.FCONST_1:
case Opcodes.FCONST_2:
push(Opcodes.FLOAT);
break;
case Opcodes.DCONST_0:
case Opcodes.DCONST_1:
push(Opcodes.DOUBLE);
push(Opcodes.TOP);
break;
case Opcodes.ILOAD:
case Opcodes.FLOAD:
case Opcodes.ALOAD:
push(get(iarg));
break;
case Opcodes.LLOAD:
case Opcodes.DLOAD:
push(get(iarg));
push(Opcodes.TOP);
break;
case Opcodes.IALOAD:
case Opcodes.BALOAD:
case Opcodes.CALOAD:
case Opcodes.SALOAD:
pop(2);
push(Opcodes.INTEGER);
break;
case Opcodes.LALOAD:
case Opcodes.D2L:
pop(2);
push(Opcodes.LONG);
push(Opcodes.TOP);
break;
case Opcodes.FALOAD:
pop(2);
push(Opcodes.FLOAT);
break;
8.2. ANALYZERADAPTER.JAVA 439
case Opcodes.DALOAD:
case Opcodes.L2D:
pop(2);
push(Opcodes.DOUBLE);
push(Opcodes.TOP);
break;
case Opcodes.AALOAD:
pop(1);
t1 = pop();
pushDesc(((String) t1).substring(1));
break;
case Opcodes.ISTORE:
case Opcodes.FSTORE:
case Opcodes.ASTORE:
t1 = pop();
set(iarg, t1);
if(iarg > 0)
{
t2 = get(iarg - 1);
if(t2 == Opcodes.LONG || t2 == Opcodes.DOUBLE)
{
set(iarg - 1, Opcodes.TOP);
}
}
break;
case Opcodes.LSTORE:
case Opcodes.DSTORE:
pop(1);
t1 = pop();
set(iarg, t1);
set(iarg + 1, Opcodes.TOP);
if(iarg > 0)
{
t2 = get(iarg - 1);
if(t2 == Opcodes.LONG || t2 == Opcodes.DOUBLE)
{
set(iarg - 1, Opcodes.TOP);
}
}
break;
case Opcodes.IASTORE:
case Opcodes.BASTORE:
case Opcodes.CASTORE:
case Opcodes.SASTORE:
case Opcodes.FASTORE:
case Opcodes.AASTORE:
pop(3);
break;
case Opcodes.LASTORE:
case Opcodes.DASTORE:
440 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
pop(4);
break;
case Opcodes.POP:
case Opcodes.IFEQ:
case Opcodes.IFNE:
case Opcodes.IFLT:
case Opcodes.IFGE:
case Opcodes.IFGT:
case Opcodes.IFLE:
case Opcodes.IRETURN:
case Opcodes.FRETURN:
case Opcodes.ARETURN:
case Opcodes.TABLESWITCH:
case Opcodes.LOOKUPSWITCH:
case Opcodes.ATHROW:
case Opcodes.MONITORENTER:
case Opcodes.MONITOREXIT:
case Opcodes.IFNULL:
case Opcodes.IFNONNULL:
pop(1);
break;
case Opcodes.POP2:
case Opcodes.IF_ICMPEQ:
case Opcodes.IF_ICMPNE:
case Opcodes.IF_ICMPLT:
case Opcodes.IF_ICMPGE:
case Opcodes.IF_ICMPGT:
case Opcodes.IF_ICMPLE:
case Opcodes.IF_ACMPEQ:
case Opcodes.IF_ACMPNE:
case Opcodes.LRETURN:
case Opcodes.DRETURN:
pop(2);
break;
case Opcodes.DUP:
t1 = pop();
push(t1);
push(t1);
break;
case Opcodes.DUP_X1:
t1 = pop();
t2 = pop();
push(t1);
push(t2);
push(t1);
break;
case Opcodes.DUP_X2:
t1 = pop();
t2 = pop();
t3 = pop();
8.2. ANALYZERADAPTER.JAVA 441
push(t1);
push(t3);
push(t2);
push(t1);
break;
case Opcodes.DUP2:
t1 = pop();
t2 = pop();
push(t2);
push(t1);
push(t2);
push(t1);
break;
case Opcodes.DUP2_X1:
t1 = pop();
t2 = pop();
t3 = pop();
push(t2);
push(t1);
push(t3);
push(t2);
push(t1);
break;
case Opcodes.DUP2_X2:
t1 = pop();
t2 = pop();
t3 = pop();
t4 = pop();
push(t2);
push(t1);
push(t4);
push(t3);
push(t2);
push(t1);
break;
case Opcodes.SWAP:
t1 = pop();
t2 = pop();
push(t1);
push(t2);
break;
case Opcodes.IADD:
case Opcodes.ISUB:
case Opcodes.IMUL:
case Opcodes.IDIV:
case Opcodes.IREM:
case Opcodes.IAND:
case Opcodes.IOR:
case Opcodes.IXOR:
case Opcodes.ISHL:
442 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
case Opcodes.ISHR:
case Opcodes.IUSHR:
case Opcodes.L2I:
case Opcodes.D2I:
case Opcodes.FCMPL:
case Opcodes.FCMPG:
pop(2);
push(Opcodes.INTEGER);
break;
case Opcodes.LADD:
case Opcodes.LSUB:
case Opcodes.LMUL:
case Opcodes.LDIV:
case Opcodes.LREM:
case Opcodes.LAND:
case Opcodes.LOR:
case Opcodes.LXOR:
pop(4);
push(Opcodes.LONG);
push(Opcodes.TOP);
break;
case Opcodes.FADD:
case Opcodes.FSUB:
case Opcodes.FMUL:
case Opcodes.FDIV:
case Opcodes.FREM:
case Opcodes.L2F:
case Opcodes.D2F:
pop(2);
push(Opcodes.FLOAT);
break;
case Opcodes.DADD:
case Opcodes.DSUB:
case Opcodes.DMUL:
case Opcodes.DDIV:
case Opcodes.DREM:
pop(4);
push(Opcodes.DOUBLE);
push(Opcodes.TOP);
break;
case Opcodes.LSHL:
case Opcodes.LSHR:
case Opcodes.LUSHR:
pop(3);
push(Opcodes.LONG);
push(Opcodes.TOP);
break;
case Opcodes.IINC:
set(iarg, Opcodes.INTEGER);
break;
8.2. ANALYZERADAPTER.JAVA 443
case Opcodes.I2L:
case Opcodes.F2L:
pop(1);
push(Opcodes.LONG);
push(Opcodes.TOP);
break;
case Opcodes.I2F:
pop(1);
push(Opcodes.FLOAT);
break;
case Opcodes.I2D:
case Opcodes.F2D:
pop(1);
push(Opcodes.DOUBLE);
push(Opcodes.TOP);
break;
case Opcodes.F2I:
case Opcodes.ARRAYLENGTH:
case Opcodes.INSTANCEOF:
pop(1);
push(Opcodes.INTEGER);
break;
case Opcodes.LCMP:
case Opcodes.DCMPL:
case Opcodes.DCMPG:
pop(4);
push(Opcodes.INTEGER);
break;
case Opcodes.JSR:
case Opcodes.RET:
throw new RuntimeException("JSR/RET are not supported");
case Opcodes.GETSTATIC:
pushDesc(sarg);
break;
case Opcodes.PUTSTATIC:
pop(sarg);
break;
case Opcodes.GETFIELD:
pop(1);
pushDesc(sarg);
break;
case Opcodes.PUTFIELD:
pop(sarg);
pop();
break;
case Opcodes.NEW:
push(labels.get(0));
break;
case Opcodes.NEWARRAY:
pop();
444 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
switch(iarg)
{
case Opcodes.T_BOOLEAN:
pushDesc("[Z");
break;
case Opcodes.T_CHAR:
pushDesc("[C");
break;
case Opcodes.T_BYTE:
pushDesc("[B");
break;
case Opcodes.T_SHORT:
pushDesc("[S");
break;
case Opcodes.T_INT:
pushDesc("[I");
break;
case Opcodes.T_FLOAT:
pushDesc("[F");
break;
case Opcodes.T_DOUBLE:
pushDesc("[D");
break;
// case Opcodes.T_LONG:
default:
pushDesc("[J");
break;
}
break;
case Opcodes.ANEWARRAY:
pop();
if(sarg.charAt(0) == [)
{
pushDesc("[" + sarg);
}
else
{
pushDesc("[L" + sarg + ";");
}
break;
case Opcodes.CHECKCAST:
pop();
if(sarg.charAt(0) == [)
{
pushDesc(sarg);
}
else
{
push(sarg);
}
8.3. CODESIZEEVALUATOR.JAVA 445
break;
// case Opcodes.MULTIANEWARRAY:
default:
pop(iarg);
pushDesc(sarg);
break;
}
labels = null;
}
}
8.3 CodeSizeEvaluator.java
(MethodAdapter [314]) (Opcodes [387])
CodeSizeEvaluator.java
import clojure.asm.Label;
import clojure.asm.MethodAdapter;
import clojure.asm.MethodVisitor;
import clojure.asm.Opcodes;
/**
* A {@link MethodAdapter} that can be used to approximate method size.
*
* @author Eugene Kuleshov
*/
public class CodeSizeEvaluator extends MethodAdapter implements Opcodes{
minSize += 3;
maxSize += 3;
if(mv != null)
{
mv.visitTypeInsn(opcode, desc);
}
}
maxSize += 8;
}
if(mv != null)
{
mv.visitJumpInsn(opcode, label);
}
}
{
mv.visitTableSwitchInsn(min, max, dflt, labels);
}
}
8.4 EmptyVisitor.java
(ClassVisitor [229]) (FieldVisitor [263]) (MethodVisitor [317]) (AnnotationVis-
itor [163])
EmptyVisitor.java
import clojure.asm.AnnotationVisitor;
import clojure.asm.Attribute;
import clojure.asm.ClassVisitor;
import clojure.asm.FieldVisitor;
import clojure.asm.Label;
import clojure.asm.MethodVisitor;
/**
* An empty implementation of the ASM visitor interfaces.
*
450 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
8.5 GeneratorAdapter.java
(LocalVariablesSorter [484])
GeneratorAdapter.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import clojure.asm.ClassVisitor;
import clojure.asm.Label;
import clojure.asm.MethodVisitor;
454 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
import clojure.asm.Opcodes;
import clojure.asm.Type;
/**
* A {@link clojure.asm.MethodAdapter} with convenient methods to
* generate code. For example, using this adapter, the class below
* <p/>
* <pre>
* public class Example {
* public static void main(String[] args) {
* System.out.println("Hello world!");
* }
* }
* </pre>
* <p/>
* can be generated as follows:
* <p/>
* <pre>
* ClassWriter cw = new ClassWriter(true);
* cw.visit(V1_1, ACC_PUBLIC, "Example", null,
* "java/lang/Object", null);
* <p/>
* Method m = Method.getMethod("void <init> ()");
* GeneratorAdapter mg =
* new GeneratorAdapter(ACC_PUBLIC, m, null, null, cw);
* mg.loadThis();
* mg.invokeConstructor(Type.getType(Object.class), m);
* mg.returnValue();
* mg.endMethod();
* <p/>
* m = Method.getMethod("void main (String[])");
* mg = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, m, null, null, cw);
* mg.getStatic(Type.getType(System.class), "out",
* Type.getType(PrintStream.class));
* mg.push("Hello world!");
* mg.invokeVirtual(Type.getType(PrintStream.class),
* Method.getMethod("void println (String)"));
* mg.returnValue();
* mg.endMethod();
* <p/>
* cw.visitEnd();
* </pre>
*
* @author Juozas Baliuka
* @author Chris Nokleberg
* @author Eric Bruneton
*/
public class GeneratorAdapter extends LocalVariablesSorter{
Type.getObjectType("java/lang/Byte");
/**
* Constant for the {@link #math math} method.
*/
456 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Constant for the {@link #math math} method.
*/
public final static int SUB = Opcodes.ISUB;
/**
* Constant for the {@link #math math} method.
*/
public final static int MUL = Opcodes.IMUL;
/**
* Constant for the {@link #math math} method.
*/
public final static int DIV = Opcodes.IDIV;
/**
* Constant for the {@link #math math} method.
*/
public final static int REM = Opcodes.IREM;
/**
* Constant for the {@link #math math} method.
*/
public final static int NEG = Opcodes.INEG;
/**
* Constant for the {@link #math math} method.
*/
public final static int SHL = Opcodes.ISHL;
/**
* Constant for the {@link #math math} method.
*/
public final static int SHR = Opcodes.ISHR;
/**
* Constant for the {@link #math math} method.
*/
public final static int USHR = Opcodes.IUSHR;
/**
* Constant for the {@link #math math} method.
*/
public final static int AND = Opcodes.IAND;
/**
* Constant for the {@link #math math} method.
*/
8.5. GENERATORADAPTER.JAVA 457
/**
* Constant for the {@link #math math} method.
*/
public final static int XOR = Opcodes.IXOR;
/**
* Constant for the {@link #ifCmp ifCmp} method.
*/
public final static int EQ = Opcodes.IFEQ;
/**
* Constant for the {@link #ifCmp ifCmp} method.
*/
public final static int NE = Opcodes.IFNE;
/**
* Constant for the {@link #ifCmp ifCmp} method.
*/
public final static int LT = Opcodes.IFLT;
/**
* Constant for the {@link #ifCmp ifCmp} method.
*/
public final static int GE = Opcodes.IFGE;
/**
* Constant for the {@link #ifCmp ifCmp} method.
*/
public final static int GT = Opcodes.IFGT;
/**
* Constant for the {@link #ifCmp ifCmp} method.
*/
public final static int LE = Opcodes.IFLE;
/**
* Access flags of the method visited by this adapter.
*/
private final int access;
/**
* Return type of the method visited by this adapter.
*/
private final Type returnType;
/**
* Argument types of the method visited by this adapter.
*/
458 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Types of the local variables of the method visited by this adapter.
*/
private final List localTypes = new ArrayList();
/**
* Creates a new {@link GeneratorAdapter}.
*
* @param mv the method visitor to which this adapter delegates
* calls.
* @param access the methods access flags (see {@link Opcodes}).
* @param name the methods name.
* @param desc the methods descriptor (see {@link Type Type}).
*/
public GeneratorAdapter(
final MethodVisitor mv,
final int access,
final String name,
final String desc){
super(access, desc, mv);
this.access = access;
this.returnType = Type.getReturnType(desc);
this.argumentTypes = Type.getArgumentTypes(desc);
}
/**
* Creates a new {@link GeneratorAdapter}.
*
* @param access access flags of the adapted method.
* @param method the adapted method.
* @param mv the method visitor to which this adapter delegates
* calls.
*/
public GeneratorAdapter(
final int access,
final Method method,
final MethodVisitor mv){
super(access, method.getDescriptor(), mv);
this.access = access;
this.returnType = method.getReturnType();
this.argumentTypes = method.getArgumentTypes();
}
/**
* Creates a new {@link GeneratorAdapter}.
*
* @param access access flags of the adapted method.
* @param method the adapted method.
8.5. GENERATORADAPTER.JAVA 459
/**
* Returns the internal names of the given types.
*
* @param types a set of types.
* @return the internal names of the given types.
*/
private static String[] getInternalNames(final Type[] types){
if(types == null)
{
return null;
}
String[] names = new String[types.length];
for(int i = 0; i < names.length; ++i)
{
names[i] = types[i].getInternalName();
}
return names;
}
// -------------------------------------------------------------------
// Instructions to push constants on the stack
// -------------------------------------------------------------------
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final boolean value){
push(value ? 1 : 0);
460 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final int value){
if(value >= -1 && value <= 5)
{
mv.visitInsn(Opcodes.ICONST_0 + value);
}
else if(value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE)
{
mv.visitIntInsn(Opcodes.BIPUSH, value);
}
else if(value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
{
mv.visitIntInsn(Opcodes.SIPUSH, value);
}
else
{
mv.visitLdcInsn(new Integer(value));
}
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final long value){
if(value == 0L || value == 1L)
{
mv.visitInsn(Opcodes.LCONST_0 + (int) value);
}
else
{
mv.visitLdcInsn(new Long(value));
}
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final float value){
int bits = Float.floatToIntBits(value);
if(bits == 0L || bits == 0x3f800000 || bits == 0x40000000)
8.5. GENERATORADAPTER.JAVA 461
{ // 0..2
mv.visitInsn(Opcodes.FCONST_0 + (int) value);
}
else
{
mv.visitLdcInsn(new Float(value));
}
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final double value){
long bits = Double.doubleToLongBits(value);
if(bits == 0L || bits == 0x3ff0000000000000L)
{ // +0.0d and 1.0d
mv.visitInsn(Opcodes.DCONST_0 + (int) value);
}
else
{
mv.visitLdcInsn(new Double(value));
}
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack. May be
* <tt>null</tt>.
*/
public void push(final String value){
if(value == null)
{
mv.visitInsn(Opcodes.ACONST_NULL);
}
else
{
mv.visitLdcInsn(value);
}
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final Type value){
if(value == null)
462 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
{
mv.visitInsn(Opcodes.ACONST_NULL);
}
else
{
mv.visitLdcInsn(value);
}
}
// -------------------------------------------------------------------
// Instructions to load and store method arguments
// ------------------------------------------------------------------
/**
* Returns the index of the given method argument in the frames local
* variables array.
*
* @param arg the index of a method argument.
* @return the index of the given method argument in the frames local
* variables array.
*/
private int getArgIndex(final int arg){
int index = (access & Opcodes.ACC_STATIC) == 0 ? 1 : 0;
for(int i = 0; i < arg; i++)
{
index += argumentTypes[i].getSize();
}
return index;
}
/**
* Generates the instruction to push a local variable on the stack.
*
* @param type the type of the local variable to be loaded.
* @param index an index in the frames local variables array.
*/
private void loadInsn(final Type type, final int index){
mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), index);
}
/**
* Generates the instruction to store the top stack value in a local
* variable.
*
* @param type the type of the local variable to be stored.
* @param index an index in the frames local variables array.
*/
private void storeInsn(final Type type, final int index){
mv.visitVarInsn(type.getOpcode(Opcodes.ISTORE), index);
}
8.5. GENERATORADAPTER.JAVA 463
/**
* Generates the instruction to load this on the stack.
*/
public void loadThis(){
if((access & Opcodes.ACC_STATIC) != 0)
{
throw new IllegalStateException(
"no this pointer within static method");
}
mv.visitVarInsn(Opcodes.ALOAD, 0);
}
/**
* Generates the instruction to load the given method argument
* on the stack.
*
* @param arg the index of a method argument.
*/
public void loadArg(final int arg){
loadInsn(argumentTypes[arg], getArgIndex(arg));
}
/**
* Generates the instructions to load the given method arguments
* on the stack.
*
* @param arg the index of the first method argument to be loaded.
* @param count the number of method arguments to be loaded.
*/
public void loadArgs(final int arg, final int count){
int index = getArgIndex(arg);
for(int i = 0; i < count; ++i)
{
Type t = argumentTypes[arg + i];
loadInsn(t, index);
index += t.getSize();
}
}
/**
* Generates the instructions to load all the method arguments
* on the stack.
*/
public void loadArgs(){
loadArgs(0, argumentTypes.length);
}
/**
* Generates the instructions to load all the method arguments
464 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Generates the instruction to store the top stack value in the given
* method argument.
*
* @param arg the index of a method argument.
*/
public void storeArg(final int arg){
storeInsn(argumentTypes[arg], getArgIndex(arg));
}
// -------------------------------------------------------------------
// Instructions to load and store local variables
// -------------------------------------------------------------------
/**
* Returns the type of the given local variable.
*
* @param local a local variable identifier, as returned by
* {@link LocalVariablesSorter#newLocal(Type) newLocal()}.
* @return the type of the given local variable.
*/
public Type getLocalType(final int local){
return (Type) localTypes.get(local - firstLocal);
}
/**
8.5. GENERATORADAPTER.JAVA 465
/**
* Generates the instruction to load the given local variable
* on the stack.
*
* @param local a local variable identifier, as returned by
* {@link LocalVariablesSorter#newLocal(Type) newLocal()}.
* @param type the type of this local variable.
*/
public void loadLocal(final int local, final Type type){
setLocalType(local, type);
loadInsn(type, local);
}
/**
* Generates the instruction to store the top stack value in the given
* local variable.
*
* @param local a local variable identifier, as returned by
* {@link LocalVariablesSorter#newLocal(Type) newLocal()}.
*/
public void storeLocal(final int local){
storeInsn(getLocalType(local), local);
}
/**
* Generates the instruction to store the top stack value in the given
* local variable.
*
* @param local a local variable identifier, as returned by
* {@link LocalVariablesSorter#newLocal(Type) newLocal()}.
* @param type the type of this local variable.
*/
public void storeLocal(final int local, final Type type){
setLocalType(local, type);
storeInsn(type, local);
}
/**
* Generates the instruction to load an element from an array.
*
466 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Generates the instruction to store an element in an array.
*
* @param type the type of the array element to be stored.
*/
public void arrayStore(final Type type){
mv.visitInsn(type.getOpcode(Opcodes.IASTORE));
}
// -------------------------------------------------------------------
// Instructions to manage the stack
// -------------------------------------------------------------------
/**
* Generates a POP instruction.
*/
public void pop(){
mv.visitInsn(Opcodes.POP);
}
/**
* Generates a POP2 instruction.
*/
public void pop2(){
mv.visitInsn(Opcodes.POP2);
}
/**
* Generates a DUP instruction.
*/
public void dup(){
mv.visitInsn(Opcodes.DUP);
}
/**
* Generates a DUP2 instruction.
*/
public void dup2(){
mv.visitInsn(Opcodes.DUP2);
}
/**
* Generates a DUP_X1 instruction.
*/
8.5. GENERATORADAPTER.JAVA 467
/**
* Generates a DUP_X2 instruction.
*/
public void dupX2(){
mv.visitInsn(Opcodes.DUP_X2);
}
/**
* Generates a DUP2_X1 instruction.
*/
public void dup2X1(){
mv.visitInsn(Opcodes.DUP2_X1);
}
/**
* Generates a DUP2_X2 instruction.
*/
public void dup2X2(){
mv.visitInsn(Opcodes.DUP2_X2);
}
/**
* Generates a SWAP instruction.
*/
public void swap(){
mv.visitInsn(Opcodes.SWAP);
}
/**
* Generates the instructions to swap the top two stack values.
*
* @param prev type of the top - 1 stack value.
* @param type type of the top stack value.
*/
public void swap(final Type prev, final Type type){
if(type.getSize() == 1)
{
if(prev.getSize() == 1)
{
swap(); // same as dupX1(), pop();
}
else
{
dupX2();
pop();
}
468 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
}
else
{
if(prev.getSize() == 1)
{
dup2X1();
pop2();
}
else
{
dup2X2();
pop2();
}
}
}
// -------------------------------------------------------------------
// Instructions to do mathematical and logical operations
// -------------------------------------------------------------------
/**
* Generates the instruction to do the specified mathematical or logical
* operation.
*
* @param op a mathematical or logical operation. Must be one of ADD,
* SUB, MUL, DIV, REM, NEG, SHL, SHR, USHR, AND, OR, XOR.
* @param type the type of the operand(s) for this operation.
*/
public void math(final int op, final Type type){
mv.visitInsn(type.getOpcode(op));
}
/**
* Generates the instructions to compute the bitwise negation of the top
* stack value.
*/
public void not(){
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IXOR);
}
/**
* Generates the instruction to increment the given local variable.
*
* @param local the local variable to be incremented.
* @param amount the amount by which the local variable must be
* incremented.
*/
public void iinc(final int local, final int amount){
mv.visitIincInsn(local, amount);
8.5. GENERATORADAPTER.JAVA 469
/**
* Generates the instructions to cast a numerical value from one type to
* another.
*
* @param from the type of the top stack value
* @param to the type into which this value must be cast.
*/
public void cast(final Type from, final Type to){
if(from != to)
{
if(from == Type.DOUBLE_TYPE)
{
if(to == Type.FLOAT_TYPE)
{
mv.visitInsn(Opcodes.D2F);
}
else if(to == Type.LONG_TYPE)
{
mv.visitInsn(Opcodes.D2L);
}
else
{
mv.visitInsn(Opcodes.D2I);
cast(Type.INT_TYPE, to);
}
}
else if(from == Type.FLOAT_TYPE)
{
if(to == Type.DOUBLE_TYPE)
{
mv.visitInsn(Opcodes.F2D);
}
else if(to == Type.LONG_TYPE)
{
mv.visitInsn(Opcodes.F2L);
}
else
{
mv.visitInsn(Opcodes.F2I);
cast(Type.INT_TYPE, to);
}
}
else if(from == Type.LONG_TYPE)
{
if(to == Type.DOUBLE_TYPE)
{
mv.visitInsn(Opcodes.L2D);
}
470 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
// -------------------------------------------------------------------
// Instructions to do boxing and unboxing operations
// -------------------------------------------------------------------
/**
* Generates the instructions to box the top stack value. This value is
* replaced by its boxed equivalent on top of the stack.
*
* @param type the type of the top stack value.
*/
8.5. GENERATORADAPTER.JAVA 471
dupX1();
swap();
}
invokeConstructor(boxed, new Method("<init>",
Type.VOID_TYPE,
new Type[]{type}));
}
}
/**
* Generates the instructions to unbox the top stack value. This value is
* replaced by its unboxed equivalent on top of the stack.
*
* @param type the type of the top stack value.
*/
public void unbox(final Type type){
Type t = NUMBER_TYPE;
Method sig = null;
switch(type.getSort())
{
case Type.VOID:
return;
case Type.CHAR:
t = CHARACTER_TYPE;
sig = CHAR_VALUE;
break;
case Type.BOOLEAN:
t = BOOLEAN_TYPE;
sig = BOOLEAN_VALUE;
break;
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case Type.LONG:
sig = LONG_VALUE;
break;
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
if(sig == null)
{
checkCast(type);
}
else
{
8.5. GENERATORADAPTER.JAVA 473
checkCast(t);
invokeVirtual(t, sig);
}
}
// -------------------------------------------------------------------
// Instructions to jump to other instructions
// -------------------------------------------------------------------
/**
* Creates a new {@link Label}.
*
* @return a new {@link Label}.
*/
public Label newLabel(){
return new Label();
}
/**
* Marks the current code position with the given label.
*
* @param label a label.
*/
public void mark(final Label label){
mv.visitLabel(label);
}
/**
* Marks the current code position with a new label.
*
* @return the label that was created to mark the current code position.
*/
public Label mark(){
Label label = new Label();
mv.visitLabel(label);
return label;
}
/**
* Generates the instructions to jump to a label based on the
* comparison of the top two stack values.
*
* @param type the type of the top two stack values.
* @param mode how these values must be compared. One of EQ, NE, LT,
* GE, GT, LE.
* @param label where to jump if the comparison result is <tt>true</tt>.
*/
public void ifCmp(final Type type, final int mode, final Label label){
int intOp = -1;
switch(type.getSort())
474 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
{
case Type.LONG:
mv.visitInsn(Opcodes.LCMP);
break;
case Type.DOUBLE:
mv.visitInsn(Opcodes.DCMPG);
break;
case Type.FLOAT:
mv.visitInsn(Opcodes.FCMPG);
break;
case Type.ARRAY:
case Type.OBJECT:
switch(mode)
{
case EQ:
mv.visitJumpInsn(Opcodes.IF_ACMPEQ, label);
return;
case NE:
mv.visitJumpInsn(Opcodes.IF_ACMPNE, label);
return;
}
throw new IllegalArgumentException("Bad comparison for type "
+ type);
default:
switch(mode)
{
case EQ:
intOp = Opcodes.IF_ICMPEQ;
break;
case NE:
intOp = Opcodes.IF_ICMPNE;
break;
case GE:
intOp = Opcodes.IF_ICMPGE;
break;
case LT:
intOp = Opcodes.IF_ICMPLT;
break;
case LE:
intOp = Opcodes.IF_ICMPLE;
break;
case GT:
intOp = Opcodes.IF_ICMPGT;
break;
}
mv.visitJumpInsn(intOp, label);
return;
}
int jumpMode = mode;
switch(mode)
8.5. GENERATORADAPTER.JAVA 475
{
case GE:
jumpMode = LT;
break;
case LE:
jumpMode = GT;
break;
}
mv.visitJumpInsn(jumpMode, label);
}
/**
* Generates the instructions to jump to a label based on the
* comparison of the top two integer stack values.
*
* @param mode how these values must be compared. One of EQ, NE, LT,
* GE, GT, LE.
* @param label where to jump if the comparison result is <tt>true</tt>.
*/
public void ifICmp(final int mode, final Label label){
ifCmp(Type.INT_TYPE, mode, label);
}
/**
* Generates the instructions to jump to a label based on the
* comparison of the top integer stack value with zero.
*
* @param mode how these values must be compared. One of EQ, NE, LT,
* GE, GT, LE.
* @param label where to jump if the comparison result is <tt>true</tt>.
*/
public void ifZCmp(final int mode, final Label label){
mv.visitJumpInsn(mode, label);
}
/**
* Generates the instruction to jump to the given label if the top stack
* value is null.
*
* @param label where to jump if the condition is <tt>true</tt>.
*/
public void ifNull(final Label label){
mv.visitJumpInsn(Opcodes.IFNULL, label);
}
/**
* Generates the instruction to jump to the given label if the top stack
* value is not null.
*
* @param label where to jump if the condition is <tt>true</tt>.
476 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
*/
public void ifNonNull(final Label label){
mv.visitJumpInsn(Opcodes.IFNONNULL, label);
}
/**
* Generates the instruction to jump to the given label.
*
* @param label where to jump if the condition is <tt>true</tt>.
*/
public void goTo(final Label label){
mv.visitJumpInsn(Opcodes.GOTO, label);
}
/**
* Generates a RET instruction.
*
* @param local a local variable identifier, as returned by
* {@link LocalVariablesSorter#newLocal(Type) newLocal()}.
*/
public void ret(final int local){
mv.visitVarInsn(Opcodes.RET, local);
}
/**
* Generates the instructions for a switch statement.
*
* @param keys the switch case keys.
* @param generator a generator to generate the code for the switch
* cases.
*/
public void tableSwitch(
final int[] keys,
final TableSwitchGenerator generator){
float density;
if(keys.length == 0)
{
density = 0;
}
else
{
density = (float) keys.length
/ (keys[keys.length - 1] - keys[0] + 1);
}
tableSwitch(keys, generator, density >= 0.5f);
}
/**
* Generates the instructions for a switch statement.
*
8.5. GENERATORADAPTER.JAVA 477
labels[i] = newLabel();
}
mv.visitLookupSwitchInsn(def, keys, labels);
for(int i = 0; i < len; ++i)
{
mark(labels[i]);
generator.generateCase(keys[i], end);
}
}
}
mark(def);
generator.generateDefault();
mark(end);
}
/**
* Generates the instruction to return the top stack value to the caller.
*/
public void returnValue(){
mv.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
}
// -------------------------------------------------------------------
// Instructions to load and store fields
// -------------------------------------------------------------------
/**
* Generates a get field or set field instruction.
*
* @param opcode the instructions opcode.
* @param ownerType the class in which the field is defined.
* @param name the name of the field.
* @param fieldType the type of the field.
*/
private void fieldInsn(
final int opcode,
final Type ownerType,
final String name,
final Type fieldType){
mv.visitFieldInsn(opcode,
ownerType.getInternalName(),
name,
fieldType.getDescriptor());
}
/**
* Generates the instruction to push the value of a static field on the
* stack.
*
* @param owner the class in which the field is defined.
8.5. GENERATORADAPTER.JAVA 479
/**
* Generates the instruction to store the top stack value
* in a static field.
*
* @param owner the class in which the field is defined.
* @param name the name of the field.
* @param type the type of the field.
*/
public void putStatic(final Type owner,
final String name,
final Type type){
fieldInsn(Opcodes.PUTSTATIC, owner, name, type);
}
/**
* Generates the instruction to push the value of a non static field
* on the stack.
*
* @param owner the class in which the field is defined.
* @param name the name of the field.
* @param type the type of the field.
*/
public void getField(final Type owner,
final String name,
final Type type){
fieldInsn(Opcodes.GETFIELD, owner, name, type);
}
/**
* Generates the instruction to store the top stack value in a
* non static field.
*
* @param owner the class in which the field is defined.
* @param name the name of the field.
* @param type the type of the field.
*/
public void putField(final Type owner,
final String name,
final Type type){
fieldInsn(Opcodes.PUTFIELD, owner, name, type);
}
480 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
// -------------------------------------------------------------------
// Instructions to invoke methods
// -------------------------------------------------------------------
/**
* Generates an invoke method instruction.
*
* @param opcode the instructions opcode.
* @param type the class in which the method is defined.
* @param method the method to be invoked.
*/
private void invokeInsn(
final int opcode,
final Type type,
final Method method){
String owner = type.getSort() == Type.ARRAY
? type.getDescriptor()
: type.getInternalName();
mv.visitMethodInsn(opcode,
owner,
method.getName(),
method.getDescriptor());
}
/**
* Generates the instruction to invoke a normal method.
*
* @param owner the class in which the method is defined.
* @param method the method to be invoked.
*/
public void invokeVirtual(final Type owner, final Method method){
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
}
/**
* Generates the instruction to invoke a constructor.
*
* @param type the class in which the constructor is defined.
* @param method the constructor to be invoked.
*/
public void invokeConstructor(final Type type, final Method method){
invokeInsn(Opcodes.INVOKESPECIAL, type, method);
}
/**
* Generates the instruction to invoke a static method.
*
* @param owner the class in which the method is defined.
* @param method the method to be invoked.
8.5. GENERATORADAPTER.JAVA 481
*/
public void invokeStatic(final Type owner, final Method method){
invokeInsn(Opcodes.INVOKESTATIC, owner, method);
}
/**
* Generates the instruction to invoke an interface method.
*
* @param owner the class in which the method is defined.
* @param method the method to be invoked.
*/
public void invokeInterface(final Type owner, final Method method){
invokeInsn(Opcodes.INVOKEINTERFACE, owner, method);
}
// -------------------------------------------------------------------
// Instructions to create objects and arrays
// -------------------------------------------------------------------
/**
* Generates a type dependent instruction.
*
* @param opcode the instructions opcode.
* @param type the instructions operand.
*/
private void typeInsn(final int opcode, final Type type){
String desc;
if(type.getSort() == Type.ARRAY)
{
desc = type.getDescriptor();
}
else
{
desc = type.getInternalName();
}
mv.visitTypeInsn(opcode, desc);
}
/**
* Generates the instruction to create a new object.
*
* @param type the class of the object to be created.
*/
public void newInstance(final Type type){
typeInsn(Opcodes.NEW, type);
}
/**
* Generates the instruction to create a new array.
*
482 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
// -------------------------------------------------------------------
// Miscelaneous instructions
// -------------------------------------------------------------------
/**
* Generates the instruction to compute the length of an array.
*/
public void arrayLength(){
mv.visitInsn(Opcodes.ARRAYLENGTH);
}
/**
* Generates the instruction to throw an exception.
8.5. GENERATORADAPTER.JAVA 483
*/
public void throwException(){
mv.visitInsn(Opcodes.ATHROW);
}
/**
* Generates the instructions to create and throw an exception. The
* exception class must have a constructor with a single String argument.
*
* @param type the class of the exception to be thrown.
* @param msg the detailed message of the exception.
*/
public void throwException(final Type type, final String msg){
newInstance(type);
dup();
push(msg);
invokeConstructor(type, Method.getMethod("void <init> (String)"));
throwException();
}
/**
* Generates the instruction to check that the top stack value is of the
* given type.
*
* @param type a class or interface type.
*/
public void checkCast(final Type type){
if(!type.equals(OBJECT_TYPE))
{
typeInsn(Opcodes.CHECKCAST, type);
}
}
/**
* Generates the instruction to test if the top stack value is
* of the given type.
*
* @param type a class or interface type.
*/
public void instanceOf(final Type type){
typeInsn(Opcodes.INSTANCEOF, type);
}
/**
* Generates the instruction to get the monitor of the top stack value.
*/
public void monitorEnter(){
mv.visitInsn(Opcodes.MONITORENTER);
}
484 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Generates the instruction to release the monitor of
* the top stack value.
*/
public void monitorExit(){
mv.visitInsn(Opcodes.MONITOREXIT);
}
// -------------------------------------------------------------------
// Non instructions
// -------------------------------------------------------------------
/**
* Marks the end of the visited method.
*/
public void endMethod(){
if((access & Opcodes.ACC_ABSTRACT) == 0)
{
mv.visitMaxs(0, 0);
}
mv.visitEnd();
}
/**
* Marks the start of an exception handler.
*
* @param start beginning of the exception handlers scope
* (inclusive).
* @param end end of the exception handlers scope (exclusive).
* @param exception internal name of the type of exceptions handled
* by the handler.
*/
public void catchException(
final Label start,
final Label end,
final Type exception){
mv.visitTryCatchBlock(start, end, mark(),
exception.getInternalName());
}
}
8.6 LocalVariablesSorter.java
(MethodAdapter [314])
LocalVariablesSorter.java
8.6. LOCALVARIABLESSORTER.JAVA 485
import clojure.asm.Label;
import clojure.asm.MethodAdapter;
import clojure.asm.MethodVisitor;
import clojure.asm.Opcodes;
import clojure.asm.Type;
/**
* A {@link MethodAdapter} that renumbers local variables in their
* order of appearance. This adapter allows one to easily add new
* local variables to a method. It may be used by inheriting from
* this class, but the preferred way of using it is via delegation:
* the next visitor in the chain can indeed add new locals when needed
* by calling {@link #newLocal} on this adapter (this requires a
* reference back to this {@link LocalVariablesSorter}).
*
* @author Chris Nokleberg
* @author Eugene Kuleshov
* @author Eric Bruneton
*/
public class LocalVariablesSorter extends MethodAdapter{
/**
* Mapping from old to new local variable indexes. A local variable
* at index i of size 1 is remapped to mapping[2*i], while a local
* variable at index i of size 2 is remapped to mapping[2*i+1].
*/
private int[] mapping = new int[40];
/**
* Array used to store stack map local variable types after remapping.
*/
private Object[] newLocals = new Object[20];
/**
* Index of the first local variable, after formal parameters.
*/
protected final int firstLocal;
/**
* Index of the next local variable to be created by {@link #newLocal}.
*/
protected int nextLocal;
/**
486 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Creates a new {@link LocalVariablesSorter}.
*
* @param access access flags of the adapted method.
* @param desc the methods descriptor (see {@link Type Type}).
* @param mv the method visitor to which this adapter delegates
* calls.
*/
public LocalVariablesSorter(
final int access,
final String desc,
final MethodVisitor mv){
super(mv);
Type[] args = Type.getArgumentTypes(desc);
nextLocal = (Opcodes.ACC_STATIC & access) != 0 ? 0 : 1;
for(int i = 0; i < args.length; i++)
{
nextLocal += args[i].getSize();
}
firstLocal = nextLocal;
}
case Opcodes.DLOAD:
case Opcodes.DSTORE:
type = Type.DOUBLE_TYPE;
break;
case Opcodes.FLOAD:
case Opcodes.FSTORE:
type = Type.FLOAT_TYPE;
break;
case Opcodes.ILOAD:
case Opcodes.ISTORE:
type = Type.INT_TYPE;
break;
8.6. LOCALVARIABLESSORTER.JAVA 487
case Opcodes.ALOAD:
case Opcodes.ASTORE:
type = OBJECT_TYPE;
break;
// case RET:
default:
type = Type.VOID_TYPE;
}
mv.visitVarInsn(opcode, remap(var, type));
}
if(!changed)
{ // optimization for the case where mapping = identity
mv.visitFrame(type, nLocal, local, nStack, stack);
return;
}
488 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
// removes TOP after long and double types as well as trailing TOPs
index = 0;
number = 0;
for(int i = 0; index < newLocals.length; ++i)
{
Object t = newLocals[index++];
if(t != null && t != Opcodes.TOP)
{
newLocals[i] = t;
number = i + 1;
if(t == Opcodes.LONG || t == Opcodes.DOUBLE)
{
index += 1;
}
}
else
{
newLocals[i] = Opcodes.TOP;
}
}
// -------------
/**
* Creates a new local variable of the given type.
*
* @param type the type of the local variable to be created.
* @return the identifier of the newly created local variable.
*/
public int newLocal(final Type type){
Object t;
switch(type.getSort())
{
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
t = Opcodes.INTEGER;
break;
case Type.FLOAT:
t = Opcodes.FLOAT;
break;
case Type.LONG:
t = Opcodes.LONG;
break;
case Type.DOUBLE:
t = Opcodes.DOUBLE;
break;
case Type.ARRAY:
t = type.getDescriptor();
break;
// case Type.OBJECT:
default:
t = type.getInternalName();
break;
}
int local = nextLocal;
setLocalType(local, type);
setFrameLocal(local, t);
nextLocal += type.getSize();
return local;
}
/**
* Sets the current type of the given local variable. The default
* implementation of this method does nothing.
*
* @param local a local variable identifier, as returned by
* {@link #newLocal newLocal()}.
* @param type the type of the value being stored in the local
490 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
* variable
*/
protected void setLocalType(final int local, final Type type){
}
8.7 Method.java
Method.java
import java.util.HashMap;
import java.util.Map;
import clojure.asm.Type;
/**
* A named method descriptor.
*
* @author Juozas Baliuka
* @author Chris Nokleberg
* @author Eric Bruneton
*/
public class Method{
/**
* The method name.
*/
private final String name;
/**
* The method descriptor.
*/
private final String desc;
/**
* Maps primitive Java type names to their descriptors.
*/
private final static Map DESCRIPTORS;
492 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
static
{
DESCRIPTORS = new HashMap();
DESCRIPTORS.put("void", "V");
DESCRIPTORS.put("byte", "B");
DESCRIPTORS.put("char", "C");
DESCRIPTORS.put("double", "D");
DESCRIPTORS.put("float", "F");
DESCRIPTORS.put("int", "I");
DESCRIPTORS.put("long", "J");
DESCRIPTORS.put("short", "S");
DESCRIPTORS.put("boolean", "Z");
}
/**
* Creates a new {@link Method}.
*
* @param name the methods name.
* @param desc the methods descriptor.
*/
public Method(final String name, final String desc){
this.name = name;
this.desc = desc;
}
/**
* Creates a new {@link Method}.
*
* @param name the methods name.
* @param returnType the methods return type.
* @param argumentTypes the methods argument types.
*/
public Method(
final String name,
final Type returnType,
final Type[] argumentTypes){
this(name, Type.getMethodDescriptor(returnType, argumentTypes));
}
/**
* Returns a {@link Method} corresponding to the given Java method
* declaration.
*
* @param method a Java method declaration, without argument names, of
* the form
* "returnType name (argumentType1, ... argumentTypeN)",
* where the types are in plain Java (e.g. "int", "float",
* "java.util.List", ...). Classes of the
* java.lang package can be specified by their
8.7. METHOD.JAVA 493
/**
* Returns a {@link Method} corresponding to the given Java method
* declaration.
*
* @param method a Java method declaration, without argument
* names, of the form
* "returnType name (argumentType1,...argumentTypeN)",
* where the types are in plain Java (e.g. "int",
* "float", "java.util.List", ...). Classes of the
* java.lang package may be specified by their
* unqualified name, depending on the
* defaultPackage argument; all other classes
* names must be fully qualified.
* @param defaultPackage true if unqualified class names belong to the
* default package, or false if they correspond
* to java.lang classes. For instance "Object"
* means "Object" if this option is true, or
* "java.lang.Object" otherwise.
* @return a {@link Method} corresponding to the given Java method
* declaration.
* @throws IllegalArgumentException if <code>method</code> could not get
* parsed.
*/
public static Method getMethod(
final String method,
final boolean defaultPackage) throws IllegalArgumentException{
int space = method.indexOf( );
int start = method.indexOf((, space) + 1;
int end = method.indexOf(), start);
if(space == -1 || start == -1 || end == -1)
{
throw new IllegalArgumentException();
}
// TODO: Check validity of returnType, methodName and arguments.
String returnType = method.substring(0, space);
String methodName = method.substring(space + 1, start - 1).trim();
StringBuffer sb = new StringBuffer();
sb.append(();
494 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
int p;
do
{
String s;
p = method.indexOf(,, start);
if(p == -1)
{
s = map(method.substring(start, end).trim(), defaultPackage);
}
else
{
s = map(method.substring(start, p).trim(), defaultPackage);
start = p + 1;
}
sb.append(s);
} while(p != -1);
sb.append());
sb.append(map(returnType, defaultPackage));
return new Method(methodName, sb.toString());
}
sb.append(t);
}
else
{
sb.append(t.replace(., /));
}
sb.append(;);
}
return sb.toString();
}
/**
* Returns the name of the method described by this object.
*
* @return the name of the method described by this object.
*/
public String getName(){
return name;
}
/**
* Returns the descriptor of the method described by this object.
*
* @return the descriptor of the method described by this object.
*/
public String getDescriptor(){
return desc;
}
/**
* Returns the return type of the method described by this object.
*
* @return the return type of the method described by this object.
*/
public Type getReturnType(){
return Type.getReturnType(desc);
}
/**
* Returns the argument types of the method described by this object.
*
* @return the argument types of the method described by this object.
*/
public Type[] getArgumentTypes(){
return Type.getArgumentTypes(desc);
}
8.8 SerialVersionUIDAdder.java
(ClassAdapter [183])
SerialVersionUIDAdder.java
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import clojure.asm.ClassAdapter;
import clojure.asm.ClassVisitor;
import clojure.asm.FieldVisitor;
import clojure.asm.MethodVisitor;
import clojure.asm.Opcodes;
/**
* A {@link ClassAdapter} that adds a serial version unique identifier
* to a class if missing. Here is typical usage of this class:
* <p/>
* <pre>
* ClassWriter cw = new ClassWriter(...);
* ClassVisitor sv = new SerialVersionUIDAdder(cw);
* ClassVisitor ca = new MyClassAdapter(sv);
* new ClassReader(orginalClass).accept(ca, false);
8.8. SERIALVERSIONUIDADDER.JAVA 497
* </pre>
* <p/>
* The SVUID algorithm can be found on the java.sun.com website under
* "j2se/1.4.2/docs/guide/serialization/spec/class.html"
* <p/>
* <pre>
* The serialVersionUID is computed using the signature of a stream of
* bytes that reflect the class definition. The National Institute of
* Standards and Technology (NIST) Secure Hash Algorithm (SHA-1) is
* used to compute a signature for the stream. The first two 32-bit
* quantities are used to form a 64-bit hash. A
* java.lang.DataOutputStream is used to convert primitive data types
* to a sequence of bytes. The values input to the stream are defined
* by the Java Virtual Machine (VM) specification for classes.
* <p/>
* The sequence of items in the stream is as follows:
* <p/>
* 1. The class name written using UTF encoding.
* 2. The class modifiers written as a 32-bit integer.
* 3. The name of each interface sorted by name written using UTF
* encoding.
* 4. For each field of the class sorted by field name (except
* private static and private transient fields):
* 1. The name of the field in UTF encoding.
* 2. The modifiers of the field written as a 32-bit integer.
* 3. The descriptor of the field in UTF encoding
* 5. If a class initializer exists, write out the following:
* 1. The name of the method, <clinit>, in UTF encoding.
* 2. The modifier of the method, java.lang.reflect.Modifier.STATIC,
* written as a 32-bit integer.
* 3. The descriptor of the method, ()V, in UTF encoding.
* 6. For each non-private constructor sorted by method name and
* signature:
* 1. The name of the method, <init>, in UTF encoding.
* 2. The modifiers of the method written as a 32-bit integer.
* 3. The descriptor of the method in UTF encoding.
* 7. For each non-private method sorted by method name and signature:
* 1. The name of the method in UTF encoding.
* 2. The modifiers of the method written as a 32-bit integer.
* 3. The descriptor of the method in UTF encoding.
* 8. The SHA-1 algorithm is executed on the stream of bytes produced by
* DataOutputStream and produces five 32-bit values sha[0..4].
* <p/>
* 9. The hash value is assembled from the first and second 32-bit
* values of the SHA-1 message digest. If the result of the message
* digest, the five 32-bit words H0 H1 H2 H3 H4, is in an array of
* five int values named sha, the hash value would be computed as
* follows:
* <p/>
* long hash = ((sha[0] >>> 24) & 0xFF) |
498 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
/**
* Flag that indicates if we need to compute SVUID.
*/
protected boolean computeSVUID;
/**
* Set to true if the class already has SVUID.
*/
protected boolean hasSVUID;
/**
* Classes access flags.
*/
protected int access;
/**
* Internal name of the class
*/
protected String name;
/**
* Interfaces implemented by the class.
*/
protected String[] interfaces;
/**
* Collection of fields. (except private static and private transient
* fields)
*/
protected Collection svuidFields;
/**
* Set to true if the class has static initializer.
*/
protected boolean hasStaticInitializer;
/**
8.8. SERIALVERSIONUIDADDER.JAVA 499
/**
* Collection of non-private methods.
*/
protected Collection svuidMethods;
/**
* Creates a new {@link SerialVersionUIDAdder}.
*
* @param cv a {@link ClassVisitor} to which this visitor will delegate
* calls.
*/
public SerialVersionUIDAdder(final ClassVisitor cv){
super(cv);
svuidFields = new ArrayList();
svuidConstructors = new ArrayList();
svuidMethods = new ArrayList();
}
// -------------------------------------------------------------------
// Overriden methods
// -------------------------------------------------------------------
/*
* Visit class header and get class name, access , and interfaces
* informatoin (step 1,2, and 3) for SVUID computation.
*/
if(computeSVUID)
{
this.name = name;
this.access = access;
this.interfaces = interfaces;
}
/*
* Visit the methods and get constructor and method information
* (step 5 and 7). Also determince if there is a class initializer
* (step 6).
*/
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions){
if(computeSVUID)
{
if(name.equals("<clinit>"))
{
hasStaticInitializer = true;
}
/*
* Remembers non private constructors and methods for SVUID
* computation For constructor and method modifiers, only the
* ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC,
* ACC_FINAL, ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT and
* ACC_STRICT flags are used.
*/
int mods = access
& (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE
| Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC
| Opcodes.ACC_FINAL | Opcodes.ACC_SYNCHRONIZED
| Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT
| Opcodes.ACC_STRICT);
/*
* Gets class field information for step 4 of the alogrithm. Also
8.8. SERIALVERSIONUIDADDER.JAVA 501
/*
* Add the SVUID if class doesnt have one
*/
public void visitEnd(){
// compute SVUID and add it to the class
if(computeSVUID && !hasSVUID)
{
try
{
cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
502 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
"serialVersionUID",
"J",
null,
new Long(computeSVUID()));
}
catch(Throwable e)
{
throw new RuntimeException("Error while computing SVUID for "
+ name, e);
}
}
super.visitEnd();
}
// -------------------------------------------------------------------
// Utility methods
// -------------------------------------------------------------------
/**
* Returns the value of SVUID if the class doesnt have one already.
* Please note that 0 is returned if the class already has SVUID, thus
* use <code>isHasSVUID</code> to determine if the class already had
* an SVUID.
*
* @return Returns the serial version UID
* @throws IOException
*/
protected long computeSVUID() throws IOException{
ByteArrayOutputStream bos = null;
DataOutputStream dos = null;
long svuid = 0;
try
{
bos = new ByteArrayOutputStream();
dos = new DataOutputStream(bos);
/*
* 1. The class name written using UTF encoding.
*/
dos.writeUTF(name.replace(/, .));
/*
* 2. The class modifiers written as a 32-bit integer.
*/
dos.writeInt(access
& (Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL
| Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT));
8.8. SERIALVERSIONUIDADDER.JAVA 503
/*
* 3. The name of each interface sorted by name written using UTF
* encoding.
*/
Arrays.sort(interfaces);
for(int i = 0; i < interfaces.length; i++)
{
dos.writeUTF(interfaces[i].replace(/, .));
}
/*
* 4. For each field of the class sorted by field name (except
* private static and private transient fields):
*
* 1. The name of the field in UTF encoding. 2. The modifiers
* of the field written as a 32-bit integer. 3. The descriptor
* of the field in UTF encoding
*
* Note that field signatutes are not dot separated. Method and
* constructor signatures are dot separated. Go figure...
*/
writeItems(svuidFields, dos, false);
/*
* 5. If a class initializer exists, write out the following: 1.
* The name of the method, <clinit>, in UTF encoding. 2. The
* modifier of the method, java.lang.reflect.Modifier.STATIC,
* written as a 32-bit integer. 3. The descriptor of the method,
* ()V, in UTF encoding.
*/
if(hasStaticInitializer)
{
dos.writeUTF("<clinit>");
dos.writeInt(Opcodes.ACC_STATIC);
dos.writeUTF("()V");
} // if..
/*
* 6. For each non-private constructor sorted by method name
* and signature: 1. The name of the method, <init>, in UTF
* encoding. 2. The modifiers of the method written as a
* 32-bit integer. 3. The descriptor of the method in UTF
* encoding.
*/
writeItems(svuidConstructors, dos, true);
/*
* 7. For each non-private method sorted by method name and
* signature: 1. The name of the method in UTF encoding. 2. The
* modifiers of the method written as a 32-bit integer. 3. The
504 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
dos.flush();
/*
* 8. The SHA-1 algorithm is executed on the stream of bytes
* produced by DataOutputStream and produces five 32-bit values
* sha[0..4].
*/
byte[] hashBytes = computeSHAdigest(bos.toByteArray());
/*
* 9. The hash value is assembled from the first and second
* 32-bit values of the SHA-1 message digest. If the result
* of the message digest, the five 32-bit words H0 H1 H2 H3 H4,
* is in an array of five int values named sha, the hash value
* would be computed as follows:
*
* long hash = ((sha[0] >>> 24) & 0xFF) |
* ((sha[0] >>> 16) & 0xFF) << 8 |
* ((sha[0] >>> 8) & 0xFF) << 16 |
* ((sha[0] >>> 0) & 0xFF) << 24 |
* ((sha[1] >>> 24) & 0xFF) << 32 |
* ((sha[1] >>> 16) & 0xFF) << 40 |
* ((sha[1] >>> 8) & 0xFF) << 48 |
* ((sha[1] >>> 0) & 0xFF) << 56;
*/
for(int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--)
{
svuid = (svuid << 8) | (hashBytes[i] & 0xFF);
}
}
finally
{
// close the stream (if open)
if(dos != null)
{
dos.close();
}
}
return svuid;
}
/**
* Returns the SHA-1 message digest of the given value.
*
* @param value the value whose SHA message digest must be computed.
8.8. SERIALVERSIONUIDADDER.JAVA 505
/**
* Sorts the items in the collection and writes it to the data output
* stream
*
* @param itemCollection collection of items
* @param dos a <code>DataOutputStream</code> value
* @param dotted a <code>boolean</code> value
* @throws IOException if an error occurs
*/
private void writeItems(
final Collection itemCollection,
final DataOutputStream dos,
final boolean dotted) throws IOException{
int size = itemCollection.size();
Item items[] = (Item[]) itemCollection.toArray(new Item[size]);
Arrays.sort(items);
for(int i = 0; i < size; i++)
{
dos.writeUTF(items[i].name);
dos.writeInt(items[i].access);
dos.writeUTF(dotted
? items[i].desc.replace(/, .)
: items[i].desc);
}
}
// -------------------------------------------------------------------
// Inner classes
// -------------------------------------------------------------------
String name;
int access;
String desc;
506 CHAPTER 8. JVM/CLOJURE/ASM/COMMONS
8.9 StaticInitMerger.java
(ClassAdapter [183])
StaticInitMerger.java
import clojure.asm.ClassAdapter;
import clojure.asm.ClassVisitor;
import clojure.asm.MethodVisitor;
import clojure.asm.Opcodes;
/**
* A {@link ClassAdapter} that merges clinit methods into a single one.
*
* @author Eric Bruneton
*/
public class StaticInitMerger extends ClassAdapter{
if(clinit == null)
{
clinit = cv.visitMethod(a, name, desc, null, null);
}
clinit.visitMethodInsn(Opcodes.INVOKESTATIC, this.name, n, desc);
}
else
{
mv = cv.visitMethod(access, name, desc, signature, exceptions);
}
return mv;
}
}
}
8.10 TableSwitchGenerator.java
TableSwitchGenerator.java
import clojure.asm.Label;
/**
* A code generator for switch statements.
*
* @author Juozas Baliuka
* @author Chris Nokleberg
* @author Eric Bruneton
*/
public interface TableSwitchGenerator{
/**
* Generates the code for a switch case.
*
* @param key the switch case key.
* @param end a label that corresponds to the end of the switch
* statement.
*/
void generateCase(int key, Label end);
/**
* Generates the code for the default switch case.
*/
void generateDefault();
}
-
Chapter 9
jvm/clojure/lang/
9.1 AFn.java
(IFn [774])
AFn.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 25, 2006 4:05:37 PM */
package clojure.lang;
509
510 CHAPTER 9. JVM/CLOJURE/LANG/
throws Exception{
return throwArity(0);
}
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 5:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 6:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 7:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 8:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 9:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
9.1. AFN.JAVA 515
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 10:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 11:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 12:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 13:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
516 CHAPTER 9. JVM/CLOJURE/LANG/
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 14:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 15:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 16:
return ifn.invoke(arglist.first()
9.1. AFN.JAVA 517
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 17:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 18:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
518 CHAPTER 9. JVM/CLOJURE/LANG/
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 19:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
case 20:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
9.2. AFUNCTION.JAVA 519
, Util.ret1(
(arglist = arglist.next()).first(),arglist = null)
);
default:
return ifn.invoke(arglist.first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, (arglist = arglist.next()).first()
, RT.seqToArray(
Util.ret1(arglist.next(),arglist = null)));
}
}
9.2 AFunction.java
(AFn [509]) (IObj [800]) (Comparator [1723]) (Fn [772]) (Serializable [1723])
AFunction.java
/*
\getchunk{Clojure Copyright}
520 CHAPTER 9. JVM/CLOJURE/LANG/
*/
/* rich Dec 16, 2008 */
package clojure.lang;
import java.io.Serializable;
import java.util.Comparator;
Number n = (Number) o;
return n.intValue();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
}
9.3 Agent.java
(ARef [553])
Agent.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Nov 17, 2007 */
9.3. AGENT.JAVA 521
package clojure.lang;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
void execute(){
try
{
if(solo)
soloExecutor.execute(this);
else
pooledExecutor.execute(this);
}
catch(Throwable error)
{
if(agent.errorHandler != null)
{
try
{
agent.errorHandler.invoke(agent, error);
9.3. AGENT.JAVA 523
}
catch(Throwable e) {} // ignore errorHandler errors
}
}
}
if(error == null)
{
releasePendingSends();
}
else
{
nested.set(null); // allow errorHandler to send
if(action.agent.errorHandler != null)
{
try
{
action.agent.errorHandler.invoke(action.agent,
error);
}
catch(Throwable e) {} // ignore errorHandler errors
}
if(action.agent.errorMode == CONTINUE)
{
error = null;
}
}
while(!popped)
{
ActionQueue prior = action.agent.aq.get();
next = new ActionQueue(prior.q.pop(), error);
popped = action.agent.aq.compareAndSet(prior, next);
}
return errorMode;
}
if(clearActions)
aq.set(ActionQueue.EMPTY);
else
{
boolean restarted = false;
ActionQueue prior = null;
while(!restarted)
{
prior = aq.get();
restarted =
aq.compareAndSet(prior, new ActionQueue(prior.q, null));
}
if(prior.q.count() > 0)
((Action) prior.q.peek()).execute();
}
return newState;
}
return this;
}
-
9.4. AMAPENTRY.JAVA 527
9.4 AMapEntry.java
(APersistentVector [541]) (IMapEntry [798])
AMapEntry.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 1, 2008 */
package clojure.lang;
import java.io.StringWriter;
/*
}
530 CHAPTER 9. JVM/CLOJURE/LANG/
9.5 APersistentMap.java
(AFn [509]) (IPersistentMap [801]) (Map [1723]) (Iterable [1723]) (Serializable
[1723]) (MapEquivalence [850])
APersistentMap.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.Serializable;
import java.util.*;
return ret;
}
if(!found ||
!Util.equals(e.getValue(), m.get(e.getKey())))
return false;
}
return true;
}
if(m.size() != size())
return false;
if(!found ||
!Util.equiv(e.getValue(), m.get(e.getKey())))
return false;
}
532 CHAPTER 9. JVM/CLOJURE/LANG/
return true;
}
public int hashCode(){
if(_hash == -1)
{
this._hash = mapHash(this);
}
return _hash;
}
// java.util.Map implementation
/*
// java.util.Collection implementation
-
538 CHAPTER 9. JVM/CLOJURE/LANG/
9.6 APersistentSet.java
(AFn [509]) (IPersistentSet [802]) (Collection [1723]) (Set [1723]) (Serializable
[1723])
APersistentSet.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
return get(arg1);
}
for(Object aM : m)
{
if(!contains(aM))
return false;
}
// for(ISeq s = seq(); s != null; s = s.rest())
// {
// if(!m.contains(s.first()))
// return false;
// }
return true;
}
return count();
}
9.7 APersistentVector.java
(AFn [509]) (IPersistentVector [802]) (Iterable [1723]) (List [1723]) (RandomAc-
cess [1723]) (Comparable [1723]) (Serializable [1723])
APersistentVector.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Dec 18, 2007 */
package clojure.lang;
import java.io.Serializable;
import java.util.*;
if(count() > 0)
return new RSeq(this, count() - 1);
return null;
}
return true;
return true;
// java.util.Collection implementation
return true;
}
return false;
}
return null;
}
9.8 AReference.java
(IReference [804])
AReference.java
/*
9.9. AREF.JAVA 553
\getchunk{Clojure Copyright}
*/
/* rich Dec 31, 2008 */
package clojure.lang;
public AReference() {
this(null);
}
9.9 ARef.java
(AReference [552]) (IRef [805])
ARef.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jan 1, 2009 */
package clojure.lang;
554 CHAPTER 9. JVM/CLOJURE/LANG/
import java.util.Map;
public ARef(){
super();
}
return this;
}
-
556 CHAPTER 9. JVM/CLOJURE/LANG/
9.10 ArityException.java
(IllegalArgumentException [1723])
ArityException.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
/**
* @since 1.3
*/
public class ArityException extends IllegalArgumentException {
9.11 ArrayChunk.java
(IChunk [772]) (Serializable [1723])
ArrayChunk.java
/*
\getchunk{Clojure Copyright}
*/
/* rich May 24, 2009 */
package clojure.lang;
import java.io.Serializable;
9.11. ARRAYCHUNK.JAVA 557
-
558 CHAPTER 9. JVM/CLOJURE/LANG/
9.12 ArraySeq.java
(ASeq [571]) (IndexedSeq [799]) (IReduce [804])
ArraySeq.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jun 19, 2006 */
package clojure.lang;
import java.lang.reflect.Array;
return -1;
}
return -1;
}
}
return -1;
}
return -1;
}
}
return -1;
}
return -1;
}
}
this.array = array;
this.i = i;
}
if (o.equals(array[j])) return j - i;
return -1;
}
}
for (int j = i; j < array.length; j++)
if (o.equals(array[j])) return j - i;
return -1;
}
9.13 ASeq.java
(Obj [947]) (ISeq [805]) (List [1723]) (Serializable [1723])
ASeq.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.Serializable;
import java.util.*;
protected ASeq(){
}
// if(m == null)
// return null;
// return m.seq();
//}
// java.util.Collection implementation
a[i] = s.first();
}
if(a.length > count())
a[count()] = null;
return a;
}
else
return toArray();
}
9.14 Associative.java
(IPersistentCollection [800]) (ILookup [797])
Associative.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.15. ATOM.JAVA 577
9.15 Atom.java
(ARef [553])
Atom.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jan 1, 2009 */
package clojure.lang;
import java.util.concurrent.atomic.AtomicReference;
{
notifyWatches(v, newv);
return newv;
}
}
}
9.16 ATransientMap.java
(AFn [509]) (ITransientMap [808])
ATransientMap.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.util.Map;
import clojure.lang.PersistentHashMap.INode;
}
else if(o instanceof IPersistentVector)
{
IPersistentVector v = (IPersistentVector) o;
if(v.count() != 2)
throw new IllegalArgumentException(
"Vector arg to map conj must be a pair");
return assoc(v.nth(0), v.nth(1));
}
9.17 ATransientSet.java
(AFn [509]) (ITransientSet [809])
ATransientSet.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
ATransientSet(ITransientMap impl) {
this.impl = impl;
}
9.18 BigInt.java
(Number [1723])
BigInt.java
/*
\getchunk{Clojure Copyright}
*/
/* chouser Jun 23, 2010 */
package clojure.lang;
import java.math.BigInteger;
///// java.lang.Number:
-
9.19. BINDING.JAVA 585
9.19 Binding.java
Binding.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.20 Box.java
Box.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 27, 2006 8:40:19 PM */
package clojure.lang;
9.21 ChunkBuffer.java
(Counted [768])
ChunkBuffer.java
/*
\getchunk{Clojure Copyright}
*/
/* rich May 26, 2009 */
package clojure.lang;
9.22 ChunkedCons.java
(ASeq [571]) (IChunkedSeq [773])
ChunkedCons.java
/*
9.22. CHUNKEDCONS.JAVA 587
\getchunk{Clojure Copyright}
*/
/* rich May 25, 2009 */
package clojure.lang;
return chunkedMore().seq();
}
9.23 Compile.java
Compile.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.IOException;
// Compiles libs and generates class files stored within the directory
// named by the Java System property "clojure.compile.path". Arguments
// are strings naming the libs to be compiled. The libs and compile-path
// must all be within CLASSPATH.
if(path == null)
{
err.println("ERROR: Must set system property " + PATH_PROP +
"\nto the location for compiled .class files." +
"\nThis directory must also be on your CLASSPATH.");
System.exit(1);
}
boolean warnOnReflection =
System.getProperty(REFLECTION_WARNING_PROP, "false").equals("true");
try
{
Var.pushThreadBindings(
RT.map(compile_path,
path,
warn_on_reflection,
warnOnReflection));
-
590 CHAPTER 9. JVM/CLOJURE/LANG/
9.24 Compiler.java
(Opcodes [387])
Compiler.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Aug 21, 2007 */
package clojure.lang;
//*
import clojure.asm.*;
import clojure.asm.commons.Method;
import clojure.asm.commons.GeneratorAdapter;
//*/
/*
import org.objectweb.asm.*;
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.util.TraceClassVisitor;
import org.objectweb.asm.util.CheckClassAdapter;
//*/
import java.io.*;
import java.util.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
static
{
OBJECT_TYPE = Type.getType(Object.class);
ARG_TYPES = new Type[MAX_POSITIONAL_ARITY + 2][];
for(int i = 0; i <= MAX_POSITIONAL_ARITY; ++i)
{
Type[] a = new Type[i];
for(int j = 0; j < i; j++)
a[j] = OBJECT_TYPE;
ARG_TYPES[i] = a;
}
Type[] a = new Type[MAX_POSITIONAL_ARITY + 1];
for(int j = 0; j < MAX_POSITIONAL_ARITY; j++)
a[j] = OBJECT_TYPE;
a[MAX_POSITIONAL_ARITY] = Type.getType("[Ljava/lang/Object;");
ARG_TYPES[MAX_POSITIONAL_ARITY + 1] = a;
//symbol->localbinding
static final public Var LOCAL_ENV = Var.create(null).setDynamic();
//vector<localbinding>
static final public Var LOOP_LOCALS = Var.create().setDynamic();
//Label
static final public Var LOOP_LABEL = Var.create().setDynamic();
//vector<object>
static final public Var CONSTANTS = Var.create().setDynamic();
//IdentityHashMap
static final public Var CONSTANT_IDS = Var.create().setDynamic();
//vector<keyword>
static final public Var KEYWORD_CALLSITES = Var.create().setDynamic();
//vector<var>
static final public Var PROTOCOL_CALLSITES = Var.create().setDynamic();
594 CHAPTER 9. JVM/CLOJURE/LANG/
//set<var>
static final public Var VAR_CALLSITES = Var.create().setDynamic();
//keyword->constid
static final public Var KEYWORDS = Var.create().setDynamic();
//var->constid
static final public Var VARS = Var.create().setDynamic();
//FnFrame
static final public Var METHOD = Var.create(null).setDynamic();
//null or not
static final public Var IN_CATCH_FINALLY = Var.create(null).setDynamic();
//DynamicClassLoader
static final public Var LOADER = Var.create().setDynamic();
//String
static final public Var SOURCE =
Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
Symbol.intern("*source-path*"), "NO_SOURCE_FILE").setDynamic();
//String
static final public Var SOURCE_PATH =
Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
Symbol.intern("*file*"), "NO_SOURCE_PATH").setDynamic();
//String
static final public Var COMPILE_PATH =
Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
Symbol.intern("*compile-path*"), null).setDynamic();
//boolean
static final public Var COMPILE_FILES =
Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
Symbol.intern("*compile-files*"), Boolean.FALSE).setDynamic();
//boolean
static final public Var UNCHECKED_MATH =
9.24. COMPILER.JAVA 595
Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
Symbol.intern("*unchecked-math*"), Boolean.FALSE).setDynamic();
//Integer
static final public Var LINE = Var.create(0).setDynamic();
//Integer
static final public Var LINE_BEFORE = Var.create(0).setDynamic();
static final public Var LINE_AFTER = Var.create(0).setDynamic();
//Integer
static final public Var NEXT_LOCAL_NUM = Var.create(0).setDynamic();
//Integer
static final public Var RET_LOCAL_NUM = Var.create().setDynamic();
//PathNode chain
static final public Var CLEAR_PATH = Var.create(null).setDynamic();
public enum C{
STATEMENT, //value ignored
EXPRESSION, //value required
RETURN, //tail position relative to enclosing recur frame
EVAL
}
interface Expr{
Object eval() throws Exception;
interface IParser{
Expr parse(C context, Object form) throws Exception;
}
if (initProvided || true)
var.setMeta((IPersistentMap) meta.eval());
}
return var.setDynamic(isDynamic);
}
catch(Throwable e)
{
if(!(e instanceof CompilerException))
throw new CompilerException(source, line, e);
else
throw (CompilerException) e;
}
}
if(context == C.STATEMENT)
gen.pop();
}
if(RT.booleanCast(RT.get(mm, arglistsKey)))
{
IPersistentMap vm = v.meta();
//vm = (IPersistentMap) RT.assoc(vm,staticKey,RT.T);
//drop quote
vm = (IPersistentMap)
RT.assoc(vm,arglistsKey,
RT.second(mm.valAt(arglistsKey)));
v.setMeta(vm);
}
Object source_path = SOURCE_PATH.get();
source_path = source_path ==
null ? "NO_SOURCE_FILE" : source_path;
mm = (IPersistentMap) RT.assoc(mm, RT.LINE_KEY, LINE.get())
.assoc(RT.FILE_KEY, source_path);
if (docstring != null)
mm = (IPersistentMap) RT.assoc(mm, RT.DOC_KEY, docstring);
Expr meta =
analyze(context == C.EVAL ? context : C.EXPRESSION, mm);
return new DefExpr((String) SOURCE.deref(),
(Integer) LINE.deref(),
v, analyze(
context == C.EVAL ?
context : C.EXPRESSION,
RT.third(form),
v.sym.name),
meta, RT.count(form) == 3, isDynamic);
}
}
}
Object val(){
return k;
}
//*
public static void emitBoxReturn(ObjExpr objx,
GeneratorAdapter gen,
Class returnType){
if(returnType.isPrimitive())
{
if(returnType == boolean.class)
{
Label falseLabel = gen.newLabel();
Label endLabel = gen.newLabel();
gen.ifZCmp(GeneratorAdapter.EQ, falseLabel);
gen.getStatic(BOOLEAN_OBJECT_TYPE, "TRUE",
BOOLEAN_OBJECT_TYPE);
gen.goTo(endLabel);
gen.mark(falseLabel);
gen.getStatic(BOOLEAN_OBJECT_TYPE, "FALSE",
BOOLEAN_OBJECT_TYPE);
// NIL_EXPR.emit(C.EXPRESSION, fn, gen);
gen.mark(endLabel);
}
else if(returnType == void.class)
{
NIL_EXPR.emit(C.EXPRESSION, objx, gen);
}
else if(returnType == char.class)
{
gen.invokeStatic(CHAR_TYPE, charValueOfMethod);
}
else
{
if(returnType == int.class)
{
gen.visitInsn(I2L);
gen.invokeStatic(NUMBERS_TYPE,
Method.getMethod("Number num(long)"));
}
else if(returnType == float.class)
{
gen.visitInsn(F2D);
9.24. COMPILER.JAVA 607
gen.invokeStatic(DOUBLE_TYPE,
doubleValueOfMethod);
}
else if(returnType == double.class)
gen.invokeStatic(DOUBLE_TYPE,
doubleValueOfMethod);
else if(returnType == long.class)
gen.invokeStatic(NUMBERS_TYPE,
Method.getMethod("Number num(long)"));
else if(returnType == byte.class)
gen.invokeStatic(BYTE_TYPE,
byteValueOfMethod);
else if(returnType == short.class)
gen.invokeStatic(SHORT_TYPE,
shortValueOfMethod);
}
}
}
//*/
public static void emitUnboxArg(ObjExpr objx,
GeneratorAdapter gen,
Class paramType){
if(paramType.isPrimitive())
{
if(paramType == boolean.class)
{
gen.checkCast(BOOLEAN_TYPE);
gen.invokeVirtual(BOOLEAN_TYPE, booleanValueMethod);
// Label falseLabel = gen.newLabel();
// Label endLabel = gen.newLabel();
// gen.ifNull(falseLabel);
// gen.push(1);
// gen.goTo(endLabel);
// gen.mark(falseLabel);
// gen.push(0);
// gen.mark(endLabel);
}
else if(paramType == char.class)
{
gen.checkCast(CHAR_TYPE);
gen.invokeVirtual(CHAR_TYPE, charValueMethod);
}
else
{
Method m = null;
gen.checkCast(NUMBER_TYPE);
if(RT.booleanCast(UNCHECKED_MATH.deref()))
{
if(paramType == int.class)
608 CHAPTER 9. JVM/CLOJURE/LANG/
m = Method.getMethod(
"int uncheckedIntCast(Object)");
else if(paramType == float.class)
m = Method.getMethod(
"float uncheckedFloatCast(Object)");
else if(paramType == double.class)
m = Method.getMethod(
"double uncheckedDoubleCast(Object)");
else if(paramType == long.class)
m = Method.getMethod(
"long uncheckedLongCast(Object)");
else if(paramType == byte.class)
m = Method.getMethod(
"byte uncheckedByteCast(Object)");
else if(paramType == short.class)
m = Method.getMethod(
"short uncheckedShortCast(Object)");
}
else
{
if(paramType == int.class)
m = Method.getMethod("int intCast(Object)");
else if(paramType == float.class)
m = Method.getMethod("float floatCast(Object)");
else if(paramType == double.class)
m = Method.getMethod("double doubleCast(Object)");
else if(paramType == long.class)
m = Method.getMethod("long longCast(Object)");
else if(paramType == byte.class)
m = Method.getMethod("byte byteCast(Object)");
else if(paramType == short.class)
m = Method.getMethod("short shortCast(Object)");
}
gen.invokeStatic(RT_TYPE, m);
}
}
else
{
gen.checkCast(Type.getType(paramType));
}
}
? RT.third(form)
: RT.next(RT.next(form)));
if(!(RT.first(call) instanceof Symbol))
throw new IllegalArgumentException(
"Malformed member expression");
Symbol sym = (Symbol) RT.first(call);
Symbol tag = tagOf(form);
PersistentVector args = PersistentVector.EMPTY;
for(ISeq s = RT.next(call); s != null; s = s.next())
args =
args.cons(analyze(context == C.EVAL
? context
: C.EXPRESSION, s.first()));
if(c != null)
return
new StaticMethodExpr(source, line, tag, c,
munge(sym.name), args);
else
return
new InstanceMethodExpr(source, line, tag,
instance,
munge(sym.name), args);
}
}
}
}
catch(Exception e){
//aargh
}
}
}
}
}
else if(stringOk && form instanceof String)
c = RT.classForName((String) form);
return c;
}
/*
private static String maybeClassName(Object form, boolean stringOk){
String className = null;
if(form instanceof Symbol)
{
Symbol sym = (Symbol) form;
if(sym.ns == null) //if ns-qualified cant be classname
{
if(sym.name.indexOf(.) > 0 ||
sym.name.charAt(0) == [)
className = sym.name;
else
{
IPersistentMap imports =
(IPersistentMap)
((Var) RT.NS_IMPORTS.get()).get();
className = (String) imports.valAt(sym);
}
}
}
else if(stringOk && form instanceof String)
className = (String) form;
return className;
}
*/
static Class tagToClass(Object tag) throws Exception{
Class c = maybeClass(tag, true);
if(tag instanceof Symbol)
{
Symbol sym = (Symbol) tag;
if(sym.ns == null) //if ns-qualified cant be classname
{
if(sym.name.equals("objects"))
c = Object[].class;
else if(sym.name.equals("ints"))
c = int[].class;
else if(sym.name.equals("longs"))
612 CHAPTER 9. JVM/CLOJURE/LANG/
c = long[].class;
else if(sym.name.equals("floats"))
c = float[].class;
else if(sym.name.equals("doubles"))
c = double[].class;
else if(sym.name.equals("chars"))
c = char[].class;
else if(sym.name.equals("shorts"))
c = short[].class;
else if(sym.name.equals("bytes"))
c = byte[].class;
else if(sym.name.equals("booleans"))
c = boolean[].class;
}
}
if(c != null)
return c;
throw new IllegalArgumentException(
"Unable to resolve classname: " + tag);
}
}
gen.visitLineNumber(line, gen.mark());
gen.getStatic(Type.getType(c), fieldName,
Type.getType(field.getType()));
//if(context != C.STATEMENT)
HostExpr.emitBoxReturn(objx, gen, field.getType());
if(context == C.STATEMENT)
{
gen.pop();
}
// gen.push(className);
// gen.push(fieldName);
// gen.invokeStatic(REFLECTOR_TYPE, getStaticFieldMethod);
}
{
Class c = e.getJavaClass();
if(Util.isPrimitive(c))
return c;
}
}
catch(Exception ex)
{
throw new RuntimeException(ex);
}
return null;
}
gen.visitInsn(I2L);
}
else if(primc == long.class &&
parameterTypes[i] == int.class)
{
final MaybePrimitiveExpr pe =
(MaybePrimitiveExpr) e;
pe.emitUnboxed(C.EXPRESSION, objx, gen);
if(RT.booleanCast(UNCHECKED_MATH.deref()))
gen.invokeStatic(RT_TYPE,
Method.getMethod("int uncheckedIntCast(long)"));
else
gen.invokeStatic(RT_TYPE,
Method.getMethod("int intCast(long)"));
}
else if(primc == float.class &&
parameterTypes[i] == double.class)
{
final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e;
pe.emitUnboxed(C.EXPRESSION, objx, gen);
gen.visitInsn(F2D);
}
else if(primc == double.class &&
parameterTypes[i] == float.class)
{
final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e;
pe.emitUnboxed(C.EXPRESSION, objx, gen);
gen.visitInsn(D2F);
}
else
{
e.emit(C.EXPRESSION, objx, gen);
HostExpr.emitUnboxArg(objx, gen, parameterTypes[i]);
}
}
catch(Exception e1)
{
e1.printStackTrace(RT.errPrintWriter());
}
}
}
}
? methods.get(methodidx)
: null);
if(m != null &&
!Modifier.isPublic(m.getDeclaringClass()
.getModifiers()))
{
//public method of non-public class, try to find
// it in hierarchy
m = Reflector.getAsMethodOfPublicBase(
m.getDeclaringClass(), m);
}
method = m;
}
}
else
method = null;
else
throw (CompilerException) e;
}
}
if(context == C.RETURN)
{
ObjMethod method = (ObjMethod) METHOD.deref();
method.emitClearLocals(gen);
}
Method m = new Method(methodName,
Type.getReturnType(method),
Type.getArgumentTypes(method));
if(method.getDeclaringClass().isInterface())
gen.invokeInterface(type, m);
else
gen.invokeVirtual(type, m);
//if(context != C.STATEMENT ||
// method.getReturnType() == Void.TYPE)
HostExpr.emitBoxReturn(objx, gen, method.getReturnType());
}
else
{
target.emit(C.EXPRESSION, objx, gen);
gen.push(methodName);
emitArgsAsArray(args, objx, gen);
if(context == C.RETURN)
{
ObjMethod method = (ObjMethod) METHOD.deref();
method.emitClearLocals(gen);
}
gen.invokeStatic(REFLECTOR_TYPE, invokeInstanceMethodMethod);
}
if(context == C.STATEMENT)
gen.pop();
}
List methods =
Reflector.getMethods(c, args.count(), methodName, true);
if(methods.isEmpty())
throw new IllegalArgumentException(
"No matching method: " + methodName);
int methodidx = 0;
if(methods.size() > 1)
{
ArrayList<Class[]> params = new ArrayList();
ArrayList<Class> rets = new ArrayList();
for(int i = 0; i < methods.size(); i++)
{
java.lang.reflect.Method m =
(java.lang.reflect.Method) methods.get(i);
params.add(m.getParameterTypes());
rets.add(m.getReturnType());
}
methodidx =
getMatchingParams(methodName, params, args, rets);
}
method = (java.lang.reflect.Method)
(methodidx >= 0 ? methods.get(methodidx) : null);
if(method == null &&
RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
{
RT.errPrintWriter()
.format(
"Reflection warning, %s:%d - call to %s cant be resolved.\n",
624 CHAPTER 9. JVM/CLOJURE/LANG/
final Number n;
public final int id;
Object val(){
return n;
}
Object val(){
return v;
}
if(v == null)
return NIL_EXPR;
// Class fclass = v.getClass();
// if(fclass == Keyword.class)
// return registerKeyword((Keyword) v);
// else if(v instanceof Num)
// return new NumExpr((Num) v);
// else if(fclass == String.class)
// return new StringExpr((String) v);
// else if(fclass == Character.class)
// return new CharExpr((Character) v);
// else if(v instanceof IPersistentCollection &&
// ((IPersistentCollection) v).count() == 0)
// return new EmptyExpr(v);
else
return new ConstantExpr(v);
}
}
}
Object val(){
return val ? RT.T : RT.F;
}
Object val(){
return str;
}
if(context != C.STATEMENT)
gen.push(str);
}
gen.mark(startTry);
tryExpr.emit(context, objx, gen);
if(context != C.STATEMENT)
gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE),
retLocal);
gen.mark(endTry);
if(finallyExpr != null)
finallyExpr.emit(C.STATEMENT, objx, gen);
gen.goTo(ret);
if(finallyExpr != null)
finallyExpr.emit(C.STATEMENT, objx, gen);
gen.goTo(ret);
}
if(finallyExpr != null)
{
gen.mark(finallyLabel);
634 CHAPTER 9. JVM/CLOJURE/LANG/
HostExpr.maybeClass(RT.second(f), false);
if(c == null)
throw new IllegalArgumentException(
"Unable to resolve classname: " +
RT.second(f));
if(!(RT.third(f) instanceof Symbol))
throw new IllegalArgumentException(
"Bad binding form, expected symbol, got: "
+ RT.third(f));
Symbol sym = (Symbol) RT.third(f);
if(sym.getNamespace() != null)
throw new Exception(
"Cant bind qualified name:" + sym);
IPersistentMap dynamicBindings =
RT.map(LOCAL_ENV, LOCAL_ENV.deref(),
NEXT_LOCAL_NUM, NEXT_LOCAL_NUM.deref(),
IN_CATCH_FINALLY, RT.T);
try
{
Var.pushThreadBindings(dynamicBindings);
LocalBinding lb =
registerLocal(sym,
(Symbol)
(RT.second(f) instanceof Symbol
? RT.second(f)
: null),
null,false);
Expr handler =
(new BodyExpr.Parser())
.parse(context,
RT.next(RT.next(RT.next(f))));
catches =
catches.cons(
new CatchClause(c, lb, handler));
}
finally
{
Var.popThreadBindings();
}
caught = true;
}
else //finally
{
if(fs.next() != null)
throw new Exception(
"finally clause must be last in try expression");
try
{
Var.pushThreadBindings(
9.24. COMPILER.JAVA 637
RT.map(IN_CATCH_FINALLY, RT.T));
finallyExpr =
(new BodyExpr.Parser())
.parse(C.STATEMENT, RT.next(f));
}
finally
{
Var.popThreadBindings();
}
}
}
}
if(bodyExpr == null) {
try
{
Var.pushThreadBindings(
RT.map(NO_RECUR, true));
bodyExpr =
(new BodyExpr.Parser())
.parse(context, RT.seq(body));
}
finally
{
Var.popThreadBindings();
}
}
this.excExpr = excExpr;
}
IPersistentVector argexprs,
List<Class> rets)
throws Exception{
//presumes matching lengths
int matchIdx = -1;
boolean tied = false;
boolean foundExact = false;
for(int i = 0; i < paramlists.size(); i++)
{
boolean match = true;
ISeq aseq = argexprs.seq();
int exact = 0;
for(int p = 0; match &&
p < argexprs.count() &&
aseq != null;
++p, aseq = aseq.next())
{
Expr arg = (Expr) aseq.first();
Class aclass =
arg.hasJavaClass() ? arg.getJavaClass() : Object.class;
Class pclass = paramlists.get(i)[p];
if(arg.hasJavaClass() && aclass == pclass)
exact++;
else
match = Reflector.paramArgTypeMatch(pclass, aclass);
}
if(exact == argexprs.count())
{
if(!foundExact ||
matchIdx == -1 ||
rets.get(matchIdx).isAssignableFrom(rets.get(i)))
matchIdx = i;
foundExact = true;
}
else if(match && !foundExact)
{
if(matchIdx == -1)
matchIdx = i;
else
{
if(subsumes(paramlists.get(i), paramlists.get(matchIdx)))
{
matchIdx = i;
tied = false;
}
else if(Arrays.equals(paramlists.get(matchIdx),
paramlists.get(i)))
{
if(rets.get(matchIdx).isAssignableFrom(rets.get(i)))
matchIdx = i;
9.24. COMPILER.JAVA 641
}
else if(!(subsumes(paramlists.get(matchIdx),
paramlists.get(i))))
tied = true;
}
}
}
if(tied)
throw new IllegalArgumentException(
"More than one matching method found: " + methodName);
return matchIdx;
}
int ctoridx = 0;
if(ctors.size() > 1)
642 CHAPTER 9. JVM/CLOJURE/LANG/
{
ctoridx = getMatchingParams(c.getName(), params, args, rets);
}
this.ctor =
ctoridx >= 0 ? (Constructor) ctors.get(ctoridx) : null;
if(ctor == null &&
RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
{
RT.errPrintWriter()
.format(
"Reflection warning, %s:%d - call to %s ctor cant be resolved.\n",
SOURCE_PATH.deref(), line, c.getName());
}
}
"clojure.lang.IObj withMeta(clojure.lang.IPersistentMap)");
return thenExpr.eval();
return elseExpr.eval();
}
gen.visitLineNumber(line, gen.mark());
try
{
if(maybePrimitiveType(testExpr) == boolean.class)
{
((MaybePrimitiveExpr) testExpr)
.emitUnboxed(C.EXPRESSION, objx, gen);
gen.ifZCmp(gen.EQ, falseLabel);
}
else
{
testExpr.emit(C.EXPRESSION, objx, gen);
gen.dup();
gen.ifNull(nullLabel);
gen.getStatic(BOOLEAN_OBJECT_TYPE, "FALSE",
BOOLEAN_OBJECT_TYPE);
gen.visitJumpInsn(IF_ACMPEQ, falseLabel);
}
}
catch(Exception e)
{
throw new RuntimeException(e);
}
if(emitUnboxed)
((MaybePrimitiveExpr)thenExpr).emitUnboxed(context, objx, gen);
else
thenExpr.emit(context, objx, gen);
646 CHAPTER 9. JVM/CLOJURE/LANG/
gen.goTo(endLabel);
gen.mark(nullLabel);
gen.pop();
gen.mark(falseLabel);
if(emitUnboxed)
((MaybePrimitiveExpr)elseExpr).emitUnboxed(context, objx, gen);
else
elseExpr.emit(context, objx, gen);
gen.mark(endLabel);
}
&, "_AMPERSAND_",
*, "_STAR_",
|, "_BAR_",
{, "_LBRACE_",
}, "_RBRACE_",
[, "_LBRACK_",
], "_RBRACK_",
/, "_SLASH_",
\\, "_BSLASH_",
?, "_QMARK_");
this.tag = tag;
this.siteIndex = registerKeywordCallsite(kw.k);
}
gen.visitLineNumber(line, gen.mark());
gen.getStatic(objx.objtype,
objx.thunkNameStatic(siteIndex),
ObjExpr.ILOOKUP_THUNK_TYPE);
gen.dup(); //thunk, thunk
target.emit(C.EXPRESSION, objx, gen); //thunk,thunk,target
gen.dupX2(); //target,thunk,thunk,target
gen.invokeInterface(ObjExpr.ILOOKUP_THUNK_TYPE,
Method.getMethod("Object get(Object)"));
//target,thunk,result
gen.dupX2(); //result,target,thunk,result
gen.visitJumpInsn(IF_ACMPEQ, faultLabel); //result,target
gen.pop(); //result
gen.goTo(endLabel);
gen.mark(faultLabel); //result,target
gen.swap(); //target,result
gen.pop(); //target
gen.dup(); //target,target
gen.getStatic(objx.objtype,
objx.siteNameStatic(siteIndex),
ObjExpr.KEYWORD_LOOKUPSITE_TYPE);
//target,target,site
gen.swap(); //target,site,target
gen.invokeInterface(ObjExpr.ILOOKUP_SITE_TYPE,
Method.getMethod("clojure.lang.ILookupThunk fault(Object)"));
//target,new-thunk
gen.dup(); //target,new-thunk,new-thunk
656 CHAPTER 9. JVM/CLOJURE/LANG/
gen.putStatic(objx.objtype,
objx.thunkNameStatic(siteIndex),
ObjExpr.ILOOKUP_THUNK_TYPE); //target,new-thunk
gen.swap(); //new-thunk,target
gen.invokeInterface(ObjExpr.ILOOKUP_THUNK_TYPE,
Method.getMethod("Object get(Object)"));
//result
gen.mark(endLabel);
if(context == C.STATEMENT)
gen.pop();
}
}
//static class KeywordSiteInvokeExpr implements Expr{
// public final Expr site;
// public final Object tag;
// public final Expr target;
// public final int line;
// public final String source;
//
// public KeywordSiteInvokeExpr(String source, int line,
// Symbol tag, Expr site, Expr target){
// this.source = source;
// this.site = site;
// this.target = target;
// this.line = line;
// this.tag = tag;
// }
//
// public Object eval() throws Exception{
// try
// {
// KeywordCallSite s = (KeywordCallSite) site.eval();
// return s.thunk.invoke(s,target.eval());
// }
// catch(Throwable e)
// {
// if(!(e instanceof CompilerException))
// throw new CompilerException(source, line, e);
// else
// throw (CompilerException) e;
9.24. COMPILER.JAVA 657
// }
// }
//
// public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
// gen.visitLineNumber(line, gen.mark());
// site.emit(C.EXPRESSION, objx, gen);
// gen.dup();
// gen.getField(Type.getType(KeywordCallSite.class),
// "thunk",IFN_TYPE);
// gen.swap();
// target.emit(C.EXPRESSION, objx, gen);
//
// gen.invokeInterface(IFN_TYPE,
// new Method("invoke", OBJECT_TYPE, ARG_TYPES[2]));
// if(context == C.STATEMENT)
// gen.pop();
// }
//
// public boolean hasJavaClass() throws Exception{
// return tag != null;
// }
//
// public Class getJavaClass() throws Exception{
// return HostExpr.tagToClass(tag);
// }
//
//}
GeneratorAdapter gen){
expr.emit(C.EXPRESSION, objx, gen);
gen.instanceOf(getType(c));
}
StaticInvokeExpr(Type target,
Class retClass,
Class[] paramclasses,
Type[] paramtypes,
boolean variadic,
IPersistentVector args,Symbol tag){
this.target = target;
this.retClass = retClass;
this.paramclasses = paramclasses;
this.paramtypes = paramtypes;
this.args = args;
this.variadic = variadic;
this.tag = tag;
}
catch(Exception ex)
{
throw new RuntimeException(ex);
}
}
IPersistentVector restArgs =
RT.subvec(args,paramclasses.length - 1,args.count());
MethodExpr.emitArgsAsArray(restArgs,objx,gen);
gen.invokeStatic(Type.getType(ArraySeq.class),
Method.getMethod(
"clojure.lang.ArraySeq create(Object[])"));
}
else
MethodExpr.emitTypedArgs(objx, gen, paramclasses, args);
gen.invokeStatic(target, ms);
}
paramlist = alist;
variadic = false;
break;
}
}
if(paramlist == null)
throw new IllegalArgumentException(
"Invalid arity - cant call: " + v + " with " +
argcount + " args");
if(variadic)
{
for(int i = 0; i < paramlist.count()-2;i++)
{
Class pc = tagClass(tagOf(paramlist.nth(i)));
paramClasses.add(pc);
paramTypes.add(Type.getType(pc));
}
paramClasses.add(ISeq.class);
paramTypes.add(Type.getType(ISeq.class));
}
else
{
for(int i = 0; i < argcount;i++)
{
Class pc = tagClass(tagOf(paramlist.nth(i)));
paramClasses.add(pc);
paramTypes.add(Type.getType(pc));
}
}
String cname =
v.ns.name.name.replace(., /).replace(-,_) +
"$" + munge(v.sym.name);
Type target = Type.getObjectType(cname);
return
new StaticInvokeExpr(target,
retClass,
paramClasses
662 CHAPTER 9. JVM/CLOJURE/LANG/
.toArray(new Class[paramClasses.size()]),
paramTypes
.toArray(new Type[paramTypes.size()]),
variadic, argv, tag);
}
}
protocolOn.getName() +
" found for function: " +
fvar.sym + " of protocol: " +
pvar.sym +
" (The protocol method may have been defined before and removed.)");
}
String mname = munge(mmapVal.sym.toString());
List methods =
Reflector.getMethods(protocolOn, args.count() - 1,
mname, false);
if(methods.size() != 1)
throw new IllegalArgumentException(
"No single method: " + mname +
" of interface: " +
protocolOn.getName() +
" found for function: " + fvar.sym +
" of protocol: " + pvar.sym);
this.onMethod =
(java.lang.reflect.Method) methods.get(0);
}
}
}
this.tag = tag != null
? tag
: (fexpr instanceof VarExpr
? ((VarExpr) fexpr).tag
: null);
}
{
emitProto(context,objx,gen);
}
else
{
fexpr.emit(C.EXPRESSION, objx, gen);
gen.checkCast(IFN_TYPE);
emitArgsAndCall(0, context,objx,gen);
}
if(context == C.STATEMENT)
gen.pop();
}
Var v = ((VarExpr)fexpr).var;
gen.mark(callLabel); //target
9.24. COMPILER.JAVA 665
objx.emitVar(gen, v);
gen.invokeVirtual(VAR_TYPE,
Method.getMethod("Object getRawRoot()"));
//target, proto-fn
gen.swap();
emitArgsAndCall(1, context,objx,gen);
gen.goTo(endLabel);
gen.mark(onLabel); //target
if(protocolOn != null)
{
MethodExpr.emitTypedArgs(objx, gen,
onMethod.getParameterTypes(),
RT.subvec(args,1,args.count()));
if(context == C.RETURN)
{
ObjMethod method = (ObjMethod) METHOD.deref();
method.emitClearLocals(gen);
}
Method m = new Method(onMethod.getName(),
Type.getReturnType(onMethod),
Type.getArgumentTypes(onMethod));
gen.invokeInterface(Type.getType(protocolOn), m);
HostExpr.emitBoxReturn(objx, gen, onMethod.getReturnType());
}
gen.mark(endLabel);
}
if(context == C.RETURN)
666 CHAPTER 9. JVM/CLOJURE/LANG/
{
ObjMethod method = (ObjMethod) METHOD.deref();
method.emitClearLocals(gen);
}
gen.invokeInterface(
IFN_TYPE,
new Method("invoke",
OBJECT_TYPE,
ARG_TYPES[Math.min(MAX_POSITIONAL_ARITY + 1,
args.count())]));
}
Var v = ((VarExpr)fexpr).var;
Object arglists = RT.get(RT.meta(v), arglistsKey);
int arity = RT.count(form.next());
for(ISeq s = RT.seq(arglists); s != null; s = s.next())
{
IPersistentVector args = (IPersistentVector) s.first();
if(args.count() == arity)
{
String primc = FnMethod.primInterface(args);
if(primc != null)
return
analyze(context,
RT.listStar(Symbol.intern(".invokePrim"),
((Symbol) form.first())
.withMeta(
RT.map(
RT.TAG_KEY, Symbol.intern(primc))),
form.next()));
break;
}
}
}
args);
}
}
if(isVariadic())
{
GeneratorAdapter gen =
new GeneratorAdapter(
ACC_PUBLIC,
Method.getMethod("int getRequiredArity()"),
9.24. COMPILER.JAVA 669
null,
null,
cv);
gen.visitCode();
gen.push(variadicMethod.reqParms.count());
gen.returnValue();
gen.endMethod();
}
}
KEYWORD_CALLSITES, PersistentVector.EMPTY,
PROTOCOL_CALLSITES, PersistentVector.EMPTY,
VAR_CALLSITES, emptyVarCallSites(),
NO_RECUR, null
));
if(methodArray[i] != null)
throw new Exception(
"Cant have fixed arity function with more params "+
"than variadic function");
}
fn.methods = methods;
fn.variadicMethod = variadicMethod;
fn.keywords = (IPersistentMap) KEYWORDS.deref();
fn.vars = (IPersistentMap) VARS.deref();
fn.constants = (PersistentVector) CONSTANTS.deref();
fn.keywordCallsites =
(IPersistentVector) KEYWORD_CALLSITES.deref();
fn.protocolCallsites =
(IPersistentVector) PROTOCOL_CALLSITES.deref();
fn.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
fn.constantsID = RT.nextID();
// DynamicClassLoader loader =
// (DynamicClassLoader) LOADER.get();
// loader.registerConstants(fn.constantsID,
// fn.constants.toArray());
}
finally
{
Var.popThreadBindings();
}
fn.compile(fn.isVariadic()
? "clojure/lang/RestFn"
: "clojure/lang/AFunction",
(prims.size() == 0)?
null
:prims.toArray(new String[prims.size()]),
fn.onceOnly);
fn.getCompiledClass();
boolean isVariadic(){
return variadicMethod != null;
}
//symbol->lb
IPersistentMap fields = null;
//Keyword->KeywordExpr
IPersistentMap keywords = PersistentHashMap.EMPTY;
IPersistentMap vars = PersistentHashMap.EMPTY;
Class compiledClass;
int line;
PersistentVector constants;
int constantsID;
int altCtorDrops = 0;
IPersistentVector keywordCallsites;
IPersistentVector protocolCallsites;
IPersistentSet varCallsites;
boolean onceOnly = false;
9.24. COMPILER.JAVA 673
Object src;
Type[] ctorTypes(){
IPersistentVector tv =
isDeftype()
? PersistentVector.EMPTY
: RT.vector(IPERSISTENTMAP_TYPE);
for(ISeq s = RT.keys(closes); s != null; s = s.next())
9.24. COMPILER.JAVA 675
{
LocalBinding lb = (LocalBinding) s.first();
if(lb.getPrimitiveType() != null)
tv = tv.cons(Type.getType(lb.getPrimitiveType()));
else
tv = tv.cons(OBJECT_TYPE);
}
Type[] ret = new Type[tv.count()];
for(int i = 0; i < tv.count(); i++)
ret[i] = (Type) tv.nth(i);
return ret;
}
"*F\n" +
"+ 1 " + source + "\n" +
(String) SOURCE_PATH.deref() + "\n" +
"*L\n" +
String.format("%d#1,%d:%d\n", lineBefore,
lineAfter - lineBefore, lineBefore) +
"*E";
cv.visitSource(source, smap);
}
addAnnotation(cv, classMeta);
//static fields for constants
for(int i = 0; i < constants.count(); i++)
{
cv.visitField(ACC_PUBLIC + ACC_FINAL
+ ACC_STATIC,
constantName(i),
constantType(i).getDescriptor(),
null, null);
}
// for(int i=0;i<varCallsites.count();i++)
// {
// cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_FINAL
// , varCallsiteName(i),
// IFN_TYPE.getDescriptor(), null, null);
// }
if(constants.count() > 0)
{
emitConstants(clinitgen);
}
if(keywordCallsites.count() > 0)
emitKeywordCallsites(clinitgen);
/*
for(int i=0;i<varCallsites.count();i++)
{
Label skipLabel = clinitgen.newLabel();
Label endLabel = clinitgen.newLabel();
Var var = (Var) varCallsites.nth(i);
clinitgen.push(var.ns.name.toString());
clinitgen.push(var.sym.toString());
clinitgen.invokeStatic(RT_TYPE,
Method.getMethod("clojure.lang.Var var(String,String)"));
clinitgen.dup();
clinitgen.invokeVirtual(VAR_TYPE,
Method.getMethod("boolean hasRoot()"));
clinitgen.ifZCmp(GeneratorAdapter.EQ,skipLabel);
clinitgen.invokeVirtual(VAR_TYPE,
Method.getMethod("Object getRoot()"));
clinitgen.dup();
clinitgen.instanceOf(AFUNCTION_TYPE);
clinitgen.ifZCmp(GeneratorAdapter.EQ,skipLabel);
clinitgen.checkCast(IFN_TYPE);
clinitgen.putStatic(objtype, varCallsiteName(i), IFN_TYPE);
clinitgen.goTo(endLabel);
clinitgen.mark(skipLabel);
clinitgen.pop();
clinitgen.mark(endLabel);
}
*/
clinitgen.returnValue();
clinitgen.endMethod();
if(!isDeftype())
{
cv.visitField(ACC_FINAL, "__meta",
IPERSISTENTMAP_TYPE.getDescriptor(), null, null);
}
//instance fields for closed-overs
for(ISeq s = RT.keys(closes); s != null; s = s.next())
{
678 CHAPTER 9. JVM/CLOJURE/LANG/
// if(vars.count() > 0)
// {
// ctorgen.loadThis();
// ctorgen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
// ctorgen.push(-1);
// ctorgen.visitInsn(Opcodes.IADD);
// ctorgen.putField(objtype, "__varrev__", Type.INT_TYPE);
// }
if(!isDeftype())
{
ctorgen.loadThis();
ctorgen.visitVarInsn(IPERSISTENTMAP_TYPE
.getOpcode(Opcodes.ILOAD), 1);
ctorgen.putField(objtype, "__meta", IPERSISTENTMAP_TYPE);
}
int a = isDeftype()?1:2;
for(ISeq s = RT.keys(closes); s != null; s = s.next(), ++a)
{
LocalBinding lb = (LocalBinding) s.first();
ctorgen.loadThis();
Class primc = lb.getPrimitiveType();
if(primc != null)
{
680 CHAPTER 9. JVM/CLOJURE/LANG/
ctorgen.visitVarInsn(
Type.getType(primc).getOpcode(Opcodes.ILOAD), a);
ctorgen.putField(objtype, lb.name, Type.getType(primc));
if(primc == Long.TYPE || primc == Double.TYPE)
++a;
}
else
{
ctorgen.visitVarInsn(
OBJECT_TYPE.getOpcode(Opcodes.ILOAD), a);
ctorgen.putField(objtype, lb.name, OBJECT_TYPE);
}
closesExprs =
closesExprs.cons(new LocalBindingExpr(lb, null));
}
ctorgen.visitLabel(end);
ctorgen.returnValue();
ctorgen.endMethod();
if(altCtorDrops > 0)
{
//ctor that takes closed-overs and inits base + fields
Type[] ctorTypes = ctorTypes();
Type[] altCtorTypes =
new Type[ctorTypes.length-altCtorDrops];
for(int i=0;i<altCtorTypes.length;i++)
altCtorTypes[i] = ctorTypes[i];
Method alt =
new Method("<init>", Type.VOID_TYPE, altCtorTypes);
ctorgen = new GeneratorAdapter(ACC_PUBLIC,
alt,
null,
null,
cv);
ctorgen.visitCode();
ctorgen.loadThis();
ctorgen.loadArgs();
for(int i=0;i<altCtorDrops;i++)
ctorgen.visitInsn(Opcodes.ACONST_NULL);
ctorgen.invokeConstructor(objtype,
new Method("<init>",
Type.VOID_TYPE, ctorTypes));
ctorgen.returnValue();
ctorgen.endMethod();
9.24. COMPILER.JAVA 681
if(!isDeftype())
{
//ctor that takes closed-overs but not meta
Type[] ctorTypes = ctorTypes();
Type[] noMetaCtorTypes = new Type[ctorTypes.length-1];
for(int i=1;i<ctorTypes.length;i++)
noMetaCtorTypes[i-1] = ctorTypes[i];
Method alt = new Method("<init>",
Type.VOID_TYPE,
noMetaCtorTypes);
ctorgen = new GeneratorAdapter(ACC_PUBLIC,
alt,
null,
null,
cv);
ctorgen.visitCode();
ctorgen.loadThis();
ctorgen.visitInsn(Opcodes.ACONST_NULL); //null meta
ctorgen.loadArgs();
ctorgen.invokeConstructor(objtype,
new Method("<init>",
Type.VOID_TYPE, ctorTypes));
ctorgen.returnValue();
ctorgen.endMethod();
//meta()
Method meth =
Method.getMethod("clojure.lang.IPersistentMap meta()");
gen.returnValue();
gen.endMethod();
//withMeta()
meth =
Method.getMethod(
"clojure.lang.IObj withMeta(clojure.lang.IPersistentMap)");
meth,
null,
null,
cv);
gen.visitCode();
gen.newInstance(objtype);
gen.dup();
gen.loadArg(0);
gen.invokeConstructor(objtype,
new Method("<init>",
Type.VOID_TYPE, ctorTypes));
gen.returnValue();
gen.endMethod();
}
emitMethods(cv);
if(keywordCallsites.count() > 0)
{
Method meth =
Method.getMethod(
"void swapThunk(int,clojure.lang.ILookupThunk)");
labels[i] = gen.newLabel();
}
gen.loadArg(0);
gen.visitTableSwitchInsn(0,keywordCallsites.count()-1,
endLabel,labels);
gen.mark(endLabel);
gen.returnValue();
gen.endMethod();
}
//end of class
cv.visitEnd();
bytecode = cw.toByteArray();
if(RT.booleanCast(COMPILE_FILES.deref()))
writeClassFile(internalName, bytecode);
// else
// getCompiledClass();
}
if(cc.isPrimitive())
{
Type bt;
if ( cc == boolean.class )
bt = Type.getType(Boolean.class);
else if ( cc == byte.class )
bt = Type.getType(Byte.class);
else if ( cc == char.class )
bt = Type.getType(Character.class);
else if ( cc == double.class )
bt = Type.getType(Double.class);
else if ( cc == float.class )
bt = Type.getType(Float.class);
else if ( cc == int.class )
bt = Type.getType(Integer.class);
else if ( cc == long.class )
bt = Type.getType(Long.class);
else if ( cc == short.class )
bt = Type.getType(Short.class);
else throw new RuntimeException(
"Cant embed unknown primitive in code: " + value);
gen.getStatic( bt, "TYPE", Type.getType(Class.class) );
}
else
{
gen.push(destubClassName(cc.getName()));
gen.invokeStatic(Type.getType(Class.class),
Method.getMethod("Class forName(String)"));
}
}
else if(value instanceof Symbol)
{
gen.push(((Symbol) value).ns);
gen.push(((Symbol) value).name);
gen.invokeStatic(Type.getType(Symbol.class),
Method.getMethod(
"clojure.lang.Symbol intern(String,String)"));
}
else if(value instanceof Keyword)
{
emitValue(((Keyword) value).sym, gen);
gen.invokeStatic(Type.getType(Keyword.class),
Method.getMethod(
"clojure.lang.Keyword intern(clojure.lang.Symbol)"));
}
// else if(value instanceof KeywordCallSite)
// {
// emitValue(((KeywordCallSite) value).k.sym, gen);
// gen.invokeStatic(Type.getType(KeywordCallSite.class),
// Method.getMethod(
686 CHAPTER 9. JVM/CLOJURE/LANG/
// "clojure.lang.KeywordCallSite create(clojure.lang.Symbol)"));
// }
else if(value instanceof Var)
{
Var var = (Var) value;
gen.push(var.ns.name.toString());
gen.push(var.sym.toString());
gen.invokeStatic(RT_TYPE,
Method.getMethod("clojure.lang.Var var(String,String)"));
}
else if(value instanceof IPersistentMap)
{
List entries = new ArrayList();
for(Map.Entry entry :
(Set<Map.Entry>) ((Map) value).entrySet())
{
entries.add(entry.getKey());
entries.add(entry.getValue());
}
emitListAsObjectArray(entries, gen);
gen.invokeStatic(RT_TYPE,
Method.getMethod(
"clojure.lang.IPersistentMap map(Object[])"));
}
else if(value instanceof IPersistentVector)
{
emitListAsObjectArray(value, gen);
gen.invokeStatic(RT_TYPE, Method.getMethod(
"clojure.lang.IPersistentVector vector(Object[])"));
}
else if(value instanceof ISeq ||
value instanceof IPersistentList)
{
emitListAsObjectArray(value, gen);
gen.invokeStatic(Type.getType(java.util.Arrays.class),
Method.getMethod("java.util.List asList(Object[])"));
gen.invokeStatic(Type.getType(PersistentList.class),
Method.getMethod(
"clojure.lang.IPersistentList create(java.util.List)"));
}
else
{
String cs = null;
try
{
cs = RT.printString(value);
//System.out.println(
// "WARNING SLOW CODE: " + value.getClass() +
// " -> " + cs);
}
9.24. COMPILER.JAVA 687
catch(Exception e)
{
throw new RuntimeException(
"Cant embed object in code, maybe print-dup not defined: " +
value);
}
if(cs.length() == 0)
throw new RuntimeException(
"Cant embed unreadable object in code: " + value);
if(cs.startsWith("#<"))
throw new RuntimeException(
"Cant embed unreadable object in code: " + cs);
gen.push(cs);
gen.invokeStatic(RT_TYPE, readStringMethod);
partial = false;
}
if(partial)
{
if(value instanceof IObj &&
RT.count(((IObj) value).meta()) > 0)
{
gen.checkCast(IOBJ_TYPE);
emitValue(((IObj) value).meta(), gen);
gen.checkCast(IPERSISTENTMAP_TYPE);
gen.invokeInterface(IOBJ_TYPE,
Method.getMethod(
"clojure.lang.IObj withMeta(clojure.lang.IPersistentMap)"));
}
}
}
Var.popThreadBindings();
}
}
boolean isDeftype(){
return fields != null;
}
catch(Exception e)
{
throw new RuntimeException(e);
}
return compiledClass;
}
}
else
{
gen.newInstance(objtype);
gen.dup();
gen.visitInsn(Opcodes.ACONST_NULL);
for(ISeq s = RT.seq(closesExprs); s != null; s = s.next())
{
LocalBindingExpr lbe = (LocalBindingExpr) s.first();
LocalBinding lb = lbe.b;
if(lb.getPrimitiveType() != null)
objx.emitUnboxedLocal(gen, lb);
else
objx.emitLocal(gen, lb, lbe.shouldClear);
}
gen.invokeConstructor(objtype,
new Method("<init>", Type.VOID_TYPE, ctorTypes()));
}
if(context == C.STATEMENT)
gen.pop();
}
}
else
{
val.emit(C.EXPRESSION, this, gen);
gen.putField(objtype, lb.name, OBJECT_TYPE);
}
}
gen.storeArg(lb.idx - argoff);
}
else
{
// System.out.println("use: " + rep);
}
}
}
else
{
if(primc != null)
{
gen.visitVarInsn(Type.getType(primc)
.getOpcode(Opcodes.ILOAD),
lb.idx);
HostExpr.emitBoxReturn(this, gen, primc);
}
else
{
gen.visitVarInsn(
OBJECT_TYPE.getOpcode(Opcodes.ILOAD),
lb.idx);
if(clear && lb.canBeCleared)
{
// System.out.println("clear: " + rep);
gen.visitInsn(Opcodes.ACONST_NULL);
gen.visitVarInsn(OBJECT_TYPE
.getOpcode(Opcodes.ISTORE),
lb.idx);
}
else
{
// System.out.println("use: " + rep);
}
}
}
}
}
gen.visitVarInsn(
Type.getType(primc).getOpcode(Opcodes.ILOAD), lb.idx);
}
// return Type.getType(c);
}
return OBJECT_TYPE;
}
enum PATHTYPE {
PATH, BRANCH;
}
enum PSTATE{
REQ, REST, DONE
}
return O;
if(c == long.class)
return L;
if(c == double.class)
return D;
throw new IllegalArgumentException(
"Only long and double primitives are supported");
}
));
method.prim = primInterface(parms);
if(method.prim != null)
method.prim = method.prim.replace(., /);
method.retClass = tagClass(tagOf(parms));
if(method.retClass.isPrimitive() &&
!(method.retClass == double.class ||
method.retClass == long.class))
throw new IllegalArgumentException(
"Only long and double primitives are supported");
else
{
Class pc = primClass(tagClass(tagOf(p)));
698 CHAPTER 9. JVM/CLOJURE/LANG/
if(state == PSTATE.REST)
pc = ISeq.class;
argtypes.add(Type.getType(pc));
argclasses.add(pc);
LocalBinding lb =
pc.isPrimitive()
? registerLocal(p, null,
new MethodParamExpr(pc), true)
: registerLocal(p, state == PSTATE.REST
? ISEQ
: tagOf(p), null, true);
argLocals = argLocals.cons(lb);
switch(state)
{
case REQ:
method.reqParms = method.reqParms.cons(lb);
break;
case REST:
method.restParm = lb;
state = PSTATE.DONE;
break;
default:
throw new Exception("Unexpected parameter");
}
}
}
9.24. COMPILER.JAVA 699
GeneratorAdapter gen =
new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
ms,
null,
//todo dont hardwire this
EXCEPTION_TYPES,
cv);
gen.visitCode();
Label loopLabel = gen.mark();
700 CHAPTER 9. JVM/CLOJURE/LANG/
gen.visitLineNumber(line, loopLabel);
try
{
Var.pushThreadBindings(
RT.map(LOOP_LABEL, loopLabel, METHOD, this));
emitBody(objx, gen, retClass, body);
gen.returnValue();
//gen.visitMaxs(1, 1);
gen.endMethod();
gen.returnValue();
//gen.visitMaxs(1, 1);
gen.endMethod();
GeneratorAdapter gen =
new GeneratorAdapter(ACC_PUBLIC + ACC_FINAL,
ms,
null,
//todo dont hardwire this
EXCEPTION_TYPES,
cv);
gen.visitCode();
gen.returnValue();
//gen.visitMaxs(1, 1);
gen.endMethod();
gen.returnValue();
//gen.visitMaxs(1, 1);
gen.endMethod();
}
public void doEmit(ObjExpr fn, ClassVisitor cv){
Method m =
new Method(getMethodName(), getReturnType(), getArgTypes());
GeneratorAdapter gen =
new GeneratorAdapter(ACC_PUBLIC,
m,
null,
//todo dont hardwire this
EXCEPTION_TYPES,
cv);
gen.visitCode();
gen.visitLocalVariable("this", "Ljava/lang/Object;",
null, loopLabel, end, 0);
for(ISeq lbs = argLocals.seq();
lbs != null;
lbs = lbs.next())
{
LocalBinding lb = (LocalBinding) lbs.first();
gen.visitLocalVariable(lb.name, "Ljava/lang/Object;",
null, loopLabel, end, lb.idx);
}
}
finally
{
Var.popThreadBindings();
}
gen.returnValue();
//gen.visitMaxs(1, 1);
gen.endMethod();
}
boolean isVariadic(){
return restParm != null;
}
int numParams(){
return reqParms.count() + (isVariadic() ? 1 : 0);
}
String getMethodName(){
return isVariadic()?"doInvoke":"invoke";
}
Type getReturnType(){
if(prim != null) //objx.isStatic)
return Type.getType(retClass);
704 CHAPTER 9. JVM/CLOJURE/LANG/
return OBJECT_TYPE;
}
Type[] getArgTypes(){
if(isVariadic() && reqParms.count() == MAX_POSITIONAL_ARITY)
{
Type[] ret = new Type[MAX_POSITIONAL_ARITY + 1];
for(int i = 0;i<MAX_POSITIONAL_ARITY + 1;i++)
ret[i] = OBJECT_TYPE;
return ret;
}
return ARG_TYPES[numParams()];
}
Class bc = maybePrimitiveType(be);
if(bc == retClass)
be.emitUnboxed(C.RETURN, objx, gen);
else if(retClass == long.class && bc == int.class)
{
be.emitUnboxed(C.RETURN, objx, gen);
gen.visitInsn(I2L);
}
else if(retClass == double.class && bc == float.class)
{
be.emitUnboxed(C.RETURN, objx, gen);
gen.visitInsn(F2D);
}
else if(retClass == int.class && bc == long.class)
{
be.emitUnboxed(C.RETURN, objx, gen);
gen.invokeStatic(RT_TYPE,
Method.getMethod("int intCast(long)"));
}
else if(retClass == float.class && bc == double.class)
{
be.emitUnboxed(C.RETURN, objx, gen);
gen.visitInsn(D2F);
}
else
throw new IllegalArgumentException(
"Mismatched primitive return, expected: "
+ retClass + ", had: " + be.getJavaClass());
}
else
{
body.emit(C.RETURN, objx, gen);
if(retClass == void.class)
{
gen.pop();
}
else
gen.unbox(Type.getType(retClass));
}
}
abstract int numParams();
abstract String getMethodName();
abstract Type getReturnType();
abstract Type[] getArgTypes();
GeneratorAdapter gen =
9.24. COMPILER.JAVA 707
new GeneratorAdapter(ACC_PUBLIC,
m,
null,
//todo dont hardwire this
EXCEPTION_TYPES,
cv);
gen.visitCode();
gen.returnValue();
//gen.visitMaxs(1, 1);
gen.endMethod();
}
}
// for(int i = 1; i < numParams() + 1; i++)
// {
// if(!localsUsedInCatchFinally.contains(i))
// {
// gen.visitInsn(Opcodes.ACONST_NULL);
// gen.visitVarInsn(
// OBJECT_TYPE.getOpcode(Opcodes.ISTORE), i);
// }
// }
for(int i = numParams() + 1; i < maxLocal + 1; i++)
{
if(!localsUsedInCatchFinally.contains(i))
{
LocalBinding b = (LocalBinding) RT.get(indexlocals, i);
if(b == null || maybePrimitiveType(b.init) == null)
{
gen.visitInsn(Opcodes.ACONST_NULL);
gen.visitVarInsn(
OBJECT_TYPE.getOpcode(Opcodes.ISTORE), i);
}
}
}
}
}
this.clearPathRoot = clearPathRoot;
name = munge(sym.name);
}
this.clearPath = (PathNode)CLEAR_PATH.get();
this.clearRoot = (PathNode)CLEAR_ROOT.get();
IPersistentCollection sites =
(IPersistentCollection) RT.get(CLEAR_SITES.get(),b);
if(b.idx > 0)
{
// Object dummy;
710 CHAPTER 9. JVM/CLOJURE/LANG/
if(sites != null)
{
for(ISeq s = sites.seq();s!=null;s = s.next())
{
LocalBindingExpr o = (LocalBindingExpr) s.first();
PathNode common = commonPath(clearPath,o.clearPath);
if(common != null && common.type == PATHTYPE.PATH)
o.shouldClear = false;
// else
// dummy = null;
}
}
if(clearRoot == b.clearPathRoot)
{
this.shouldClear = true;
sites = RT.conj(sites,this);
CLEAR_SITES.set(RT.assoc(CLEAR_SITES.get(), b, sites));
}
// else
// dummy = null;
}
}
Expr val){
objx.emitAssignLocal(gen, b,val);
if(context != C.STATEMENT)
objx.emitLocal(gen, b, false);
}
IPersistentVector bindings =
(IPersistentVector) RT.second(form);
if((bindings.count() % 2) != 0)
throw new IllegalArgumentException(
"Bad binding form, expected matched symbol expression pairs");
if(context == C.EVAL)
return
analyze(context,
RT.list(
RT.list(FN, PersistentVector.EMPTY,
714 CHAPTER 9. JVM/CLOJURE/LANG/
form)));
IPersistentMap dynamicBindings =
RT.map(LOCAL_ENV, LOCAL_ENV.deref(),
NEXT_LOCAL_NUM, NEXT_LOCAL_NUM.deref());
try
{
Var.pushThreadBindings(dynamicBindings);
Type.getDescriptor(primc),
null, loopLabel, end,
bi.binding.idx);
else
gen.visitLocalVariable(lname, "Ljava/lang/Object;",
null, loopLabel, end,
bi.binding.idx);
}
}
IPersistentVector bindings =
(IPersistentVector) RT.second(form);
if((bindings.count() % 2) != 0)
throw new IllegalArgumentException(
"Bad binding form, expected matched symbol expression pairs");
if(context == C.EVAL
9.24. COMPILER.JAVA 717
if(isLoop)
dynamicBindings =
dynamicBindings.assoc(LOOP_LOCALS, null);
try
{
Var.pushThreadBindings(dynamicBindings);
PersistentVector bindingInits =
PersistentVector.EMPTY;
PersistentVector loopLocals =
PersistentVector.EMPTY;
for(int i = 0; i < bindings.count(); i += 2)
{
if(!(bindings.nth(i) instanceof Symbol))
throw new IllegalArgumentException(
"Bad binding form, expected symbol, got: "
+ bindings.nth(i));
Symbol sym = (Symbol) bindings.nth(i);
if(sym.getNamespace() != null)
throw new Exception(
"Cant let qualified name: " + sym);
Expr init =
analyze(C.EXPRESSION, bindings.nth(i + 1),
sym.name);
if(isLoop)
{
if(recurMismatches != null &&
((LocalBinding)recurMismatches
718 CHAPTER 9. JVM/CLOJURE/LANG/
.nth(i/2)).recurMistmatch)
{
init =
new StaticMethodExpr("", 0, null,
RT.class, "box",
RT.vector(init));
if(RT.booleanCast(
RT.WARN_ON_REFLECTION.deref()))
RT.errPrintWriter().println(
"Auto-boxing loop arg: " + sym);
}
else if(maybePrimitiveType(init)==int.class)
init =
new StaticMethodExpr("", 0, null,
RT.class,
"longCast",
RT.vector(init));
else if(maybePrimitiveType(init) ==
float.class)
init =
new StaticMethodExpr("", 0, null,
RT.class,
"doubleCast",
RT.vector(init));
}
//sequential enhancement of env (like Lisp let*)
LocalBinding lb =
registerLocal(sym, tagOf(sym), init,false);
BindingInit bi = new BindingInit(lb, init);
bindingInits = bindingInits.cons(bi);
if(isLoop)
loopLocals = loopLocals.cons(lb);
}
if(isLoop)
LOOP_LOCALS.set(loopLocals);
Expr bodyExpr;
try {
if(isLoop)
{
PathNode root =
new PathNode(PATHTYPE.PATH,
(PathNode) CLEAR_PATH.get());
Var.pushThreadBindings(
RT.map(CLEAR_PATH,
new PathNode(PATHTYPE.PATH,root),
CLEAR_ROOT,
new PathNode(PATHTYPE.PATH,root),
NO_RECUR, null));
9.24. COMPILER.JAVA 719
}
bodyExpr =
(new BodyExpr.Parser())
.parse(isLoop
? C.RETURN
: context, body);
}
finally{
if(isLoop)
{
Var.popThreadBindings();
recurMismatches = null;
for(int i = 0;i< loopLocals.count();i++)
{
LocalBinding lb =
(LocalBinding) loopLocals.nth(i);
if(lb.recurMistmatch)
recurMismatches = loopLocals;
}
}
}
if(recurMismatches == null)
return
new LetExpr(bindingInits, bodyExpr, isLoop);
}
finally
{
Var.popThreadBindings();
}
}
}
}
ObjExpr objx,
GeneratorAdapter gen,
boolean emitUnboxed){
for(int i = 0; i < bindingInits.count(); i++)
{
BindingInit bi = (BindingInit) bindingInits.nth(i);
Class primc = maybePrimitiveType(bi.init);
if(primc != null)
{
((MaybePrimitiveExpr) bi.init)
.emitUnboxed(C.EXPRESSION, objx, gen);
gen.visitVarInsn(
Type.getType(primc)
.getOpcode(Opcodes.ISTORE),
bi.binding.idx);
}
else
{
bi.init.emit(C.EXPRESSION, objx, gen);
gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE),
bi.binding.idx);
}
}
Label loopLabel = gen.mark();
if(isLoop)
{
try
{
Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel));
if(emitUnboxed)
((MaybePrimitiveExpr)body)
.emitUnboxed(context, objx, gen);
else
body.emit(context, objx, gen);
}
finally
{
Var.popThreadBindings();
}
}
else
{
if(emitUnboxed)
((MaybePrimitiveExpr)body)
.emitUnboxed(context, objx, gen);
else
body.emit(context, objx, gen);
}
Label end = gen.mark();
// gen.visitLocalVariable("this", "Ljava/lang/Object;",
9.24. COMPILER.JAVA 721
//RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
// if(true)
throw new IllegalArgumentException
// RT.errPrintWriter().println
(//source + ":" + line +
" recur arg for primitive local: " +
lb.name +
" is not matching primitive, had: " +
(arg.hasJavaClass()
? arg.getJavaClass().getName()
:"Object") +
", needed: " +
primc.getName());
// arg.emit(C.EXPRESSION, objx, gen);
// HostExpr.emitUnboxArg(objx,gen,primc);
}
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
else
{
arg.emit(C.EXPRESSION, objx, gen);
}
}
gen.goTo(loopLabel);
}
724 CHAPTER 9. JVM/CLOJURE/LANG/
|| pc == byte.class))
mismatch = true;
}
else if(primc == double.class)
{
if(!(pc == double.class
|| pc == float.class))
mismatch = true;
}
if(mismatch)
{
lb.recurMistmatch = true;
if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
RT.errPrintWriter().println
(source + ":" + line +
" recur arg for primitive local: " +
lb.name +
" is not matching primitive, had: " +
(pc != null ? pc.getName():"Object") +
", needed: " +
primc.getName());
}
}
}
return new RecurExpr(loopLocals, args, line, source);
}
}
}
m.maxLocal = num;
NEXT_LOCAL_NUM.set(num + 1);
return num;
}
}
else if(form instanceof ISeq)
return analyzeSeq(context, (ISeq) form, name);
else if(form instanceof IPersistentVector)
return VectorExpr.parse(context,
(IPersistentVector) form);
else if(form instanceof IPersistentMap)
return MapExpr.parse(context, (IPersistentMap) form);
else if(form instanceof IPersistentSet)
return SetExpr.parse(context, (IPersistentSet) form);
// else
//throw new UnsupportedOperationException();
return new ConstantExpr(form);
}
catch(Throwable e)
{
if(!(e instanceof CompilerException))
throw new CompilerException(
(String) SOURCE_PATH.deref(), (Integer) LINE.deref(), e);
else
throw (CompilerException) e;
}
}
form.next().next()));
}
else if(namesStaticMember(sym))
{
Symbol target = Symbol.intern(sym.ns);
Class c = HostExpr.maybeClass(target, false);
if(c != null)
{
Symbol meth = Symbol.intern(sym.name);
return
preserveTag(form,
RT.listStar(DOT,
target,
meth,
form.next()));
}
}
else
{
//(s.substring 2 5) => (. s substring 2 5)
//also (package.class.name ...) (. package.class name ...)
int idx = sname.lastIndexOf(.);
// if(idx > 0 && idx < sname.length() - 1)
// {
// Symbol target = Symbol.intern(sname.substring(0, idx));
// Symbol meth = Symbol.intern(sname.substring(idx + 1));
// return RT.listStar(DOT, target, meth, form.rest());
// }
//(StringBuilder. "foo") => (new StringBuilder "foo")
//else
if(idx == sname.length() - 1)
return
RT.listStar(NEW,
Symbol.intern(sname.substring(0, idx)),
form.next());
}
}
}
}
return x;
}
ISeq form,
String name)
throws Exception{
Integer line = (Integer) LINE.deref();
if(RT.meta(form) != null && RT.meta(form).containsKey(RT.LINE_KEY))
line = (Integer) RT.meta(form).valAt(RT.LINE_KEY);
Var.pushThreadBindings(
RT.map(LINE, line));
try
{
Object me = macroexpand1(form);
if(me != form)
return analyze(context, me, name);
Object op = RT.first(form);
if(op == null)
throw new IllegalArgumentException("Cant call nil");
IFn inline = isInline(op, RT.count(RT.next(form)));
if(inline != null)
return
analyze(context,
preserveTag(form, inline.applyTo(RT.next(form))));
IParser p;
if(op.equals(FN))
return FnExpr.parse(context, form, name);
else if((p = (IParser) specials.valAt(op)) != null)
return p.parse(context, form);
else
return InvokeExpr.parse(context, form);
}
catch(Throwable e)
{
if(!(e instanceof CompilerException))
throw new CompilerException(
(String) SOURCE_PATH.deref(), (Integer) LINE.deref(), e);
else
throw (CompilerException) e;
}
finally
{
Var.popThreadBindings();
}
}
throw (Exception)e;
}
finally
{
Var.popThreadBindings();
}
}
finally
{
if(createdLoader)
Var.popThreadBindings();
}
}
IPersistentVector keywordCallsites =
(IPersistentVector) KEYWORD_CALLSITES.deref();
keywordCallsites = keywordCallsites.cons(keyword);
KEYWORD_CALLSITES.set(keywordCallsites);
return keywordCallsites.count()-1;
}
IPersistentVector protocolCallsites =
(IPersistentVector) PROTOCOL_CALLSITES.deref();
protocolCallsites = protocolCallsites.cons(v);
PROTOCOL_CALLSITES.set(protocolCallsites);
return protocolCallsites.count()-1;
}
IPersistentCollection varCallsites =
(IPersistentCollection) VAR_CALLSITES.deref();
varCallsites = varCallsites.cons(v);
VAR_CALLSITES.set(varCallsites);
// return varCallsites.count()-1;
}
xp = xp.next();
yp = yp.next();
}
return (PathNode) RT.first(xp);
}
Var v = ns.findInternedVar(Symbol.intern(sym.name));
if(v == null)
throw new Exception("No such var: " + sym);
else if(v.ns != currentNS() && !v.isPublic() && !allowPrivate)
throw new IllegalStateException(
"var: " + sym + " is not public");
return v;
}
else if(sym.name.indexOf(.) > 0 || sym.name.charAt(0) == [)
{
return RT.classForName(sym.name);
}
else if(sym.equals(NS))
return RT.NS_VAR;
else if(sym.equals(IN_NS))
return RT.IN_NS_VAR;
else
{
if(Util.equals(sym, COMPILE_STUB_SYM.get()))
return COMPILE_STUB_CLASS.get();
738 CHAPTER 9. JVM/CLOJURE/LANG/
Object o = n.getMapping(sym);
if(o == null)
{
if(RT.booleanCast(RT.ALLOW_UNRESOLVED_VARS.deref()))
{
return sym;
}
else
{
throw new Exception(
"Unable to resolve symbol: " + sym +
" in this context");
}
}
return o;
}
}
{
VARS.set(RT.assoc(varsMap, var, registerConstant(var)));
}
// if(varsMap != null && RT.get(varsMap, var) == null)
// VARS.set(RT.assoc(varsMap, var, var));
}
try
{
742 CHAPTER 9. JVM/CLOJURE/LANG/
for(Object r =
LispReader.read(pushbackReader, false, EOF, false); r != EOF;
r = LispReader.read(pushbackReader, false, EOF, false))
{
LINE_AFTER.set(pushbackReader.getLineNumber());
ret = eval(r,false);
LINE_BEFORE.set(pushbackReader.getLineNumber());
}
}
catch(LispReader.ReaderException e)
{
throw new CompilerException(sourcePath, e.line, e.getCause());
}
finally
{
Var.popThreadBindings();
}
return ret;
}
Var.pushThreadBindings(
PersistentHashMap.create(
Var.intern(
Symbol.intern("clojure.core"),
Symbol.intern("*ns*")).setDynamic(), null));
}
try
{
//generate loader class
ObjExpr objx = new ObjExpr(null);
objx.internalName =
sourcePath.replace(File.separator, "/")
.substring(0, sourcePath.lastIndexOf(.))
+ RT.LOADER_SUFFIX;
objx.objtype = Type.getObjectType(objx.internalName);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = cw;
cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER,
objx.internalName, null,
"java/lang/Object", null);
for(Object r =
LispReader.read(pushbackReader, false, EOF, false);
r != EOF;
r = LispReader.read(pushbackReader, false, EOF, false))
{
LINE_AFTER.set(pushbackReader.getLineNumber());
compile1(gen, objx, r);
LINE_BEFORE.set(pushbackReader.getLineNumber());
}
//end of load
gen.returnValue();
gen.endMethod();
for(int n = 0;n<numInits;n++)
{
GeneratorAdapter clinitgen =
new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
Method.getMethod("void __init" + n + "()"),
null,
null,
cv);
clinitgen.visitCode();
try
{
Var.pushThreadBindings(RT.map(RT.PRINT_DUP, RT.T));
for(int i = n*INITS_PER;
i < objx.constants.count() &&
i < (n+1)*INITS_PER;
746 CHAPTER 9. JVM/CLOJURE/LANG/
i++)
{
objx.emitValue(objx.constants.nth(i), clinitgen);
clinitgen.checkCast(objx.constantType(i));
clinitgen.putStatic(
objx.objtype,
objx.constantName(i),
objx.constantType(i));
}
}
finally
{
Var.popThreadBindings();
}
clinitgen.returnValue();
clinitgen.endMethod();
}
// if(objx.constants.count() > 0)
// {
// objx.emitConstants(clinitgen);
// }
for(int n = 0;n<numInits;n++)
clinitgen.invokeStatic(
objx.objtype,
Method.getMethod("void __init" + n + "()"));
clinitgen.invokeStatic(Type.getType(Compiler.class),
Method.getMethod("void pushNS()"));
clinitgen.mark(startTry);
clinitgen.invokeStatic(objx.objtype,
Method.getMethod("void load()"));
clinitgen.mark(endTry);
clinitgen.invokeStatic(VAR_TYPE,
Method.getMethod("void popThreadBindings()"));
clinitgen.goTo(end);
9.24. COMPILER.JAVA 747
clinitgen.mark(finallyLabel);
//exception should be on stack
clinitgen.invokeStatic(VAR_TYPE,
Method.getMethod("void popThreadBindings()"));
clinitgen.throwException();
clinitgen.mark(end);
clinitgen.visitTryCatchBlock(startTry, endTry,
finallyLabel, null);
//end of class
cv.visitEnd();
writeClassFile(objx.internalName, cw.toByteArray());
}
catch(LispReader.ReaderException e)
{
throw new CompilerException(sourcePath, e.line, e.getCause());
}
finally
{
Var.popThreadBindings();
}
return ret;
}
Map<IPersistentVector,java.lang.reflect.Method> mmap;
Map<IPersistentVector,Set<Class>> covariants;
rform = rform.next();
IPersistentVector fields = (IPersistentVector) rform.first();
rform = rform.next();
IPersistentMap opts = PersistentHashMap.EMPTY;
while(rform != null && rform.first() instanceof Keyword)
{
opts = opts.assoc(rform.first(), RT.second(rform));
rform = rform.next().next();
}
ObjExpr ret =
build((IPersistentVector)RT.get(opts,implementsKey,
PersistentVector.EMPTY),fields,null,tagname,
classname, (Symbol) RT.get(opts,RT.TAG_KEY),
rform, frm);
return ret;
}
}
IPersistentVector interfaces =
((IPersistentVector) RT.first(rform))
.cons(Symbol.intern("clojure.lang.IObj"));
rform = RT.next(rform);
ObjExpr ret =
build(interfaces, null, null, classname,
Symbol.intern(classname), null, rform, frm);
if(frm instanceof IObj && ((IObj) frm).meta() != null)
return new MetaExpr(ret, MapExpr
.parse(context == C.EVAL
? context
: C.EXPRESSION, ((IObj) frm).meta()));
else
return ret;
9.24. COMPILER.JAVA 749
}
}
ret.src = frm;
ret.name = className.toString();
ret.classMeta = RT.meta(className);
ret.internalName = ret.name.replace(., /);
ret.objtype = Type.getObjectType(ret.internalName);
if(thisSym != null)
ret.thisName = thisSym.name;
if(fieldSyms != null)
{
IPersistentMap fmap = PersistentHashMap.EMPTY;
Object[] closesvec = new Object[2 * fieldSyms.count()];
for(int i=0;i<fieldSyms.count();i++)
{
Symbol sym = (Symbol) fieldSyms.nth(i);
LocalBinding lb = new LocalBinding(-1, sym, null,
new MethodParamExpr(tagClass(tagOf(sym))),
false,null);
fmap = fmap.assoc(sym, lb);
closesvec[i*2] = lb;
closesvec[i*2 + 1] = lb;
}
{
Class c = (Class) resolve((Symbol) s.first());
if(!c.isInterface())
throw new IllegalArgumentException(
"only interfaces are supported, had: " + c.getName());
interfaces = interfaces.cons(c);
}
Class superClass = Object.class;
Map[] mc = gatherMethods(superClass,RT.seq(interfaces));
Map overrideables = mc[0];
Map covariants = mc[1];
ret.mmap = overrideables;
ret.covariants = covariants;
try
{
Var.pushThreadBindings(
RT.map(CONSTANTS, PersistentVector.EMPTY,
CONSTANT_IDS, new IdentityHashMap(),
KEYWORDS, PersistentHashMap.EMPTY,
VARS, PersistentHashMap.EMPTY,
KEYWORD_CALLSITES, PersistentVector.EMPTY,
PROTOCOL_CALLSITES, PersistentVector.EMPTY,
VAR_CALLSITES, emptyVarCallSites(),
NO_RECUR, null));
if(ret.isDeftype())
{
Var.pushThreadBindings(RT.map(METHOD, null,
LOCAL_ENV, ret.fields
, COMPILE_STUB_SYM, Symbol.intern(null, tagName)
, COMPILE_STUB_CLASS, stub));
}
ret.methods = methods;
ret.keywords = (IPersistentMap) KEYWORDS.deref();
ret.vars = (IPersistentMap) VARS.deref();
ret.constants = (PersistentVector) CONSTANTS.deref();
ret.constantsID = RT.nextID();
ret.keywordCallsites =
(IPersistentVector) KEYWORD_CALLSITES.deref();
ret.protocolCallsites =
(IPersistentVector) PROTOCOL_CALLSITES.deref();
ret.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
}
finally
{
if(ret.isDeftype())
Var.popThreadBindings();
Var.popThreadBindings();
}
ret.compile(slashname(superClass),inames,false);
ret.getCompiledClass();
return ret;
}
/***
* Current host interop uses reflection, which requires
* pre-existing classes. Work around this by:
* Generate a stub class that has the same interfaces and fields
* as the class we are generating.
* Use it as a type hint for this, and bind the simple name of
* the class to this stub (in resolve etc)
* Unmunge the name (using a magic prefix) on any code gen
* for classes
*/
static Class compileStub(String superName,
NewInstanceExpr ret,
String[] interfaceNames,
Object frm){
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = cw;
cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER,
COMPILE_STUB_PREFIX + "/" + ret.internalName,
null,superName,interfaceNames);
ACC_FINAL);
if(lb.getPrimitiveType() != null)
cv.visitField(access,
lb.name,
Type.getType(lb.getPrimitiveType())
.getDescriptor(),
null, null);
else
//todo - when closed-overs are fields, use more specific
// types here and in ctor and emitLocal?
cv.visitField(access,
lb.name, OBJECT_TYPE.getDescriptor(), null, null);
}
if(ret.altCtorDrops > 0)
{
Type[] ctorTypes = ret.ctorTypes();
Type[] altCtorTypes =
new Type[ctorTypes.length-ret.altCtorDrops];
for(int i=0;i<altCtorTypes.length;i++)
altCtorTypes[i] = ctorTypes[i];
Method alt = new Method("<init>",
Type.VOID_TYPE, altCtorTypes);
ctorgen = new GeneratorAdapter(ACC_PUBLIC,
alt,
null,
null,
cv);
ctorgen.visitCode();
ctorgen.loadThis();
ctorgen.loadArgs();
for(int i=0;i<ret.altCtorDrops;i++)
ctorgen.visitInsn(Opcodes.ACONST_NULL);
9.24. COMPILER.JAVA 753
ctorgen.invokeConstructor(
Type.getObjectType(
COMPILE_STUB_PREFIX + "/" + ret.internalName),
new Method("<init>", Type.VOID_TYPE, ctorTypes));
ctorgen.returnValue();
ctorgen.endMethod();
}
//end of class
cv.visitEnd();
Method target =
new Method(m.getName(),
Type.getType(m.getReturnType()),
argTypes);
GeneratorAdapter gen =
new GeneratorAdapter(ACC_PUBLIC + ACC_BRIDGE,
meth,
null,
//todo dont hardwire this
EXCEPTION_TYPES,
cv);
gen.visitCode();
gen.loadThis();
gen.loadArgs();
gen.invokeInterface(
Type.getType(m.getDeclaringClass()),target);
gen.returnValue();
gen.endMethod();
}
}
}
if(!(mm.containsKey(mk)
|| !(Modifier.isPublic(mods) || Modifier.isProtected(mods))
|| Modifier.isStatic(mods)
|| Modifier.isFinal(mods)))
{
mm.put(mk, m);
}
}
9.24. COMPILER.JAVA 755
Map<IPersistentVector,java.lang.reflect.Method> mm =
new HashMap<IPersistentVector,java.lang.reflect.Method>();
Map<IPersistentVector,Set<Class>> covariants =
new HashMap<IPersistentVector,Set<Class>>();
for(Object o : allm.entrySet())
{
Map.Entry e = (Map.Entry) o;
IPersistentVector mk = (IPersistentVector) e.getKey();
mk = (IPersistentVector) mk.pop();
java.lang.reflect.Method m =
(java.lang.reflect.Method) e.getValue();
if(mm.containsKey(mk)) //covariant return
{
Set<Class> cvs = covariants.get(mk);
if(cvs == null)
{
cvs = new HashSet<Class>();
covariants.put(mk,cvs);
}
java.lang.reflect.Method om = mm.get(mk);
if(om.getReturnType()
.isAssignableFrom(m.getReturnType()))
{
cvs.add(om.getReturnType());
mm.put(mk, m);
}
else
cvs.add(m.getReturnType());
}
else
mm.put(mk, m);
}
return new Map[]{mm,covariants};
756 CHAPTER 9. JVM/CLOJURE/LANG/
}
}
int numParams(){
return argLocals.count();
}
String getMethodName(){
return name;
}
Type getReturnType(){
return retType;
}
Type[] getArgTypes(){
return argTypes;
}
Symbol name =
(Symbol) Symbol.intern(null,
munge(dotname.name))
.withMeta(RT.meta(dotname));
IPersistentVector parms = (IPersistentVector) RT.second(form);
if(parms.count() == 0)
{
throw new IllegalArgumentException(
"Must supply at least one argument for this in: " + dotname);
}
Symbol thisName = (Symbol) parms.nth(0);
parms = RT.subvec(parms,1,parms.count());
ISeq body = RT.next(RT.next(form));
try
{
method.line = (Integer) LINE.deref();
//register as the current method and set up a new env frame
PathNode pnode =
new PathNode(PATHTYPE.PATH, (PathNode) CLEAR_PATH.get());
Var.pushThreadBindings(
RT.map(
METHOD, method,
LOCAL_ENV, LOCAL_ENV.deref(),
LOOP_LOCALS, null,
NEXT_LOCAL_NUM, 0
,CLEAR_PATH, pnode
,CLEAR_ROOT, pnode
,CLEAR_SITES, PersistentHashMap.EMPTY
));
//else
//validate unque name+arity among additional methods
method.retType = Type.getType(method.retClass);
method.exclasses = m.getExceptionTypes();
}
}
{
IPersistentMap meta = RT.meta(parms.nth(i));
addParameterAnnotation(gen, meta, i);
}
gen.visitCode();
gen.visitLineNumber(line, loopLabel);
try
{
Var.pushThreadBindings(
RT.map(LOOP_LABEL, loopLabel, METHOD, this));
gen.returnValue();
//gen.visitMaxs(1, 1);
gen.endMethod();
}
}
if(sym.name.equals("int"))
c = int.class;
else if(sym.name.equals("long"))
c = long.class;
else if(sym.name.equals("float"))
c = float.class;
else if(sym.name.equals("double"))
c = double.class;
else if(sym.name.equals("char"))
c = char.class;
else if(sym.name.equals("short"))
c = short.class;
else if(sym.name.equals("byte"))
c = byte.class;
else if(sym.name.equals("boolean"))
c = boolean.class;
else if(sym.name.equals("void"))
c = void.class;
return c;
}
this.tests = tests;
this.thens = thens;
this.line = line;
this.allKeywords = allKeywords;
}
for(Integer i : tests.keySet())
{
labels.put(i, gen.newLabel());
}
for(int i=low;i<=high;i++)
{
la[i-low] = labels.containsKey(i)
? labels.get(i)
: defaultLabel;
}
gen.visitLineNumber(line, gen.mark());
expr.emit(C.EXPRESSION, objx, gen);
gen.invokeStatic(UTIL_TYPE,hashMethod);
gen.push(shift);
gen.visitInsn(ISHR);
gen.push(mask);
gen.visitInsn(IAND);
gen.visitTableSwitchInsn(low, high, defaultLabel, la);
for(Integer i : labels.keySet())
{
gen.mark(labels.get(i));
expr.emit(C.EXPRESSION, objx, gen);
tests.get(i).emit(C.EXPRESSION, objx, gen);
if(allKeywords)
{
gen.visitJumpInsn(IF_ACMPNE, defaultLabel);
}
else
{
gen.invokeStatic(UTIL_TYPE, equalsMethod);
9.24. COMPILER.JAVA 765
gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
}
thens.get(i).emit(C.EXPRESSION,objx,gen);
gen.goTo(endLabel);
}
gen.mark(defaultLabel);
defaultExpr.emit(C.EXPRESSION, objx, gen);
gen.mark(endLabel);
if(context == C.STATEMENT)
gen.pop();
}
LocalBindingExpr testexpr =
(LocalBindingExpr) analyze(C.EXPRESSION, args.nth(0));
testexpr.shouldClear = false;
PathNode branch =
new PathNode(PATHTYPE.BRANCH, (PathNode) CLEAR_PATH.get());
for(Object o : ((Map)args.nth(6)).entrySet())
{
Map.Entry e = (Map.Entry) o;
Integer minhash = ((Number)e.getKey()).intValue();
MapEntry me = (MapEntry) e.getValue();
Expr testExpr = new ConstantExpr(me.getKey());
tests.put(minhash, testExpr);
Expr thenExpr;
try {
Var.pushThreadBindings(
RT.map(CLEAR_PATH,
new PathNode(PATHTYPE.PATH,branch)));
thenExpr = analyze(context, me.getValue());
}
766 CHAPTER 9. JVM/CLOJURE/LANG/
finally{
Var.popThreadBindings();
}
thens.put(minhash, thenExpr);
}
Expr defaultExpr;
try {
Var.pushThreadBindings(
RT.map(CLEAR_PATH,
new PathNode(PATHTYPE.PATH,branch)));
defaultExpr = analyze(context, args.nth(5));
}
finally{
Var.popThreadBindings();
}
}
}
}
9.25 Cons.java
(ASeq [571]) (Serializable [1723])
Cons.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 25, 2006 11:01:29 AM */
package clojure.lang;
9.25. CONS.JAVA 767
import java.io.Serializable;
-
768 CHAPTER 9. JVM/CLOJURE/LANG/
9.26 Counted.java
Counted.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.27 Delay.java
(IDeref [773])
Delay.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jun 28, 2007 */
package clojure.lang;
if(fn != null)
{
val = fn.invoke();
fn = null;
}
return val;
}
}
9.28 DynamicClassLoader.java
(URLClassLoader [1723])
DynamicClassLoader.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Aug 21, 2007 */
package clojure.lang;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.net.URLClassLoader;
import java.net.URL;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public DynamicClassLoader(){
//pseudo test in lieu of hasContextClassLoader()
super(EMPTY_URLS,
(Thread.currentThread().getContextClassLoader() == null ||
Thread.currentThread().getContextClassLoader() ==
ClassLoader.getSystemClassLoader())
? Compiler.class.getClassLoader()
770 CHAPTER 9. JVM/CLOJURE/LANG/
: Thread.currentThread().getContextClassLoader());
}
9.29 EnumerationSeq.java
(ASeq [571])
EnumerationSeq.java
9.29. ENUMERATIONSEQ.JAVA 771
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
import java.io.IOException;
import java.io.NotSerializableException;
import java.util.Enumeration;
EnumerationSeq(Enumeration iter){
this.iter = iter;
state = new State();
this.state.val = state;
this.state._rest = state;
}
if(state._rest == state)
synchronized(state)
{
if(state._rest == state)
{
first();
state._rest = create(iter);
}
}
return (ISeq) state._rest;
}
9.30 Fn.java
Fn.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Nov 25, 2008 */
package clojure.lang;
9.31 IChunk.java
(Indexed [799])
IChunk.java
9.32. ICHUNKEDSEQ.JAVA 773
/*
\getchunk{Clojure Copyright}
*/
/* rich Jun 18, 2009 */
package clojure.lang;
IChunk dropFirst();
9.32 IChunkedSeq.java
(ISeq [805])
IChunkedSeq.java
/*
\getchunk{Clojure Copyright}
*/
/* rich May 24, 2009 */
package clojure.lang;
9.33 IDeref.java
IDeref.java
/*
774 CHAPTER 9. JVM/CLOJURE/LANG/
\getchunk{Clojure Copyright}
*/
/* rich Feb 9, 2009 */
package clojure.lang;
9.34 IEditableCollection.java
IEditableCollection.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 17, 2009 */
package clojure.lang;
9.35 IFn.java
(Callable [1723]) (Runnable [1723])
IFn.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 25, 2006 3:54:03 PM */
package clojure.lang;
import java.util.concurrent.Callable;
throws Exception;
Object arg3);}
static public interface
OLDOD{double invokePrim(Object arg0, long arg1, double arg2,
Object arg3);}
static public interface
OLDLO{Object invokePrim(Object arg0, long arg1, double arg2,
long arg3);}
static public interface
OLDLL{long invokePrim(Object arg0, long arg1, double arg2,
long arg3);}
static public interface
OLDLD{double invokePrim(Object arg0, long arg1, double arg2,
long arg3);}
static public interface
OLDDO{Object invokePrim(Object arg0, long arg1, double arg2,
double arg3);}
static public interface
OLDDL{long invokePrim(Object arg0, long arg1, double arg2,
double arg3);}
static public interface
OLDDD{double invokePrim(Object arg0, long arg1, double arg2,
double arg3);}
static public interface
ODOOO{Object invokePrim(Object arg0, double arg1, Object arg2,
Object arg3);}
static public interface
ODOOL{long invokePrim(Object arg0, double arg1, Object arg2,
Object arg3);}
static public interface
ODOOD{double invokePrim(Object arg0, double arg1, Object arg2,
Object arg3);}
static public interface
ODOLO{Object invokePrim(Object arg0, double arg1, Object arg2,
long arg3);}
static public interface
ODOLL{long invokePrim(Object arg0, double arg1, Object arg2,
long arg3);}
static public interface
ODOLD{double invokePrim(Object arg0, double arg1, Object arg2,
long arg3);}
static public interface
ODODO{Object invokePrim(Object arg0, double arg1, Object arg2,
double arg3);}
static public interface
ODODL{long invokePrim(Object arg0, double arg1, Object arg2,
double arg3);}
static public interface
ODODD{double invokePrim(Object arg0, double arg1, Object arg2,
double arg3);}
static public interface
786 CHAPTER 9. JVM/CLOJURE/LANG/
double arg3);}
static public interface
LOLDL{long invokePrim(long arg0, Object arg1, long arg2,
double arg3);}
static public interface
LOLDD{double invokePrim(long arg0, Object arg1, long arg2,
double arg3);}
static public interface
LODOO{Object invokePrim(long arg0, Object arg1, double arg2,
Object arg3);}
static public interface
LODOL{long invokePrim(long arg0, Object arg1, double arg2,
Object arg3);}
static public interface
LODOD{double invokePrim(long arg0, Object arg1, double arg2,
Object arg3);}
static public interface
LODLO{Object invokePrim(long arg0, Object arg1, double arg2,
long arg3);}
static public interface
LODLL{long invokePrim(long arg0, Object arg1, double arg2,
long arg3);}
static public interface
LODLD{double invokePrim(long arg0, Object arg1, double arg2,
long arg3);}
static public interface
LODDO{Object invokePrim(long arg0, Object arg1, double arg2,
double arg3);}
static public interface
LODDL{long invokePrim(long arg0, Object arg1, double arg2,
double arg3);}
static public interface
LODDD{double invokePrim(long arg0, Object arg1, double arg2,
double arg3);}
static public interface
LLOOO{Object invokePrim(long arg0, long arg1, Object arg2,
Object arg3);}
static public interface
LLOOL{long invokePrim(long arg0, long arg1, Object arg2,
Object arg3);}
static public interface
LLOOD{double invokePrim(long arg0, long arg1, Object arg2,
Object arg3);}
static public interface
LLOLO{Object invokePrim(long arg0, long arg1, Object arg2,
long arg3);}
static public interface
LLOLL{long invokePrim(long arg0, long arg1, Object arg2,
long arg3);}
static public interface
9.35. IFN.JAVA 789
Object arg3);}
static public interface
LDLLO{Object invokePrim(long arg0, double arg1, long arg2,
long arg3);}
static public interface
LDLLL{long invokePrim(long arg0, double arg1, long arg2,
long arg3);}
static public interface
LDLLD{double invokePrim(long arg0, double arg1, long arg2,
long arg3);}
static public interface
LDLDO{Object invokePrim(long arg0, double arg1, long arg2,
double arg3);}
static public interface
LDLDL{long invokePrim(long arg0, double arg1, long arg2,
double arg3);}
static public interface
LDLDD{double invokePrim(long arg0, double arg1, long arg2,
double arg3);}
static public interface
LDDOO{Object invokePrim(long arg0, double arg1, double arg2,
Object arg3);}
static public interface
LDDOL{long invokePrim(long arg0, double arg1, double arg2,
Object arg3);}
static public interface
LDDOD{double invokePrim(long arg0, double arg1, double arg2,
Object arg3);}
static public interface
LDDLO{Object invokePrim(long arg0, double arg1, double arg2,
long arg3);}
static public interface
LDDLL{long invokePrim(long arg0, double arg1, double arg2,
long arg3);}
static public interface
LDDLD{double invokePrim(long arg0, double arg1, double arg2,
long arg3);}
static public interface
LDDDO{Object invokePrim(long arg0, double arg1, double arg2,
double arg3);}
static public interface
LDDDL{long invokePrim(long arg0, double arg1, double arg2,
double arg3);}
static public interface
LDDDD{double invokePrim(long arg0, double arg1, double arg2,
double arg3);}
static public interface
DOOOO{Object invokePrim(double arg0, Object arg1, Object arg2,
Object arg3);}
static public interface
792 CHAPTER 9. JVM/CLOJURE/LANG/
double arg3);}
static public interface
DLODD{double invokePrim(double arg0, long arg1, Object arg2,
double arg3);}
static public interface
DLLOO{Object invokePrim(double arg0, long arg1, long arg2,
Object arg3);}
static public interface
DLLOL{long invokePrim(double arg0, long arg1, long arg2,
Object arg3);}
static public interface
DLLOD{double invokePrim(double arg0, long arg1, long arg2,
Object arg3);}
static public interface
DLLLO{Object invokePrim(double arg0, long arg1, long arg2,
long arg3);}
static public interface
DLLLL{long invokePrim(double arg0, long arg1, long arg2,
long arg3);}
static public interface
DLLLD{double invokePrim(double arg0, long arg1, long arg2,
long arg3);}
static public interface
DLLDO{Object invokePrim(double arg0, long arg1, long arg2,
double arg3);}
static public interface
DLLDL{long invokePrim(double arg0, long arg1, long arg2,
double arg3);}
static public interface
DLLDD{double invokePrim(double arg0, long arg1, long arg2,
double arg3);}
static public interface
DLDOO{Object invokePrim(double arg0, long arg1, double arg2,
Object arg3);}
static public interface
DLDOL{long invokePrim(double arg0, long arg1, double arg2,
Object arg3);}
static public interface
DLDOD{double invokePrim(double arg0, long arg1, double arg2,
Object arg3);}
static public interface
DLDLO{Object invokePrim(double arg0, long arg1, double arg2,
long arg3);}
static public interface
DLDLL{long invokePrim(double arg0, long arg1, double arg2,
long arg3);}
static public interface
DLDLD{double invokePrim(double arg0, long arg1, double arg2,
long arg3);}
static public interface
9.35. IFN.JAVA 795
9.36 IKeywordLookup.java
IKeywordLookup.java
9.37. ILOOKUP.JAVA 797
/*
getchunk{Clojure Copyright}
*/
/* rich Oct 31, 2009 */
package clojure.lang;
9.37 ILookup.java
ILookup.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Aug 2, 2009 */
package clojure.lang;
9.38 ILookupSite.java
ILookupSite.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Nov 2, 2009 */
package clojure.lang;
9.39 ILookupThunk.java
ILookupThunk.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Nov 2, 2009 */
package clojure.lang;
9.40 IMapEntry.java
(Map.Entry [1723])
IMapEntry.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.util.Map;
Object val();
}
-
9.41. IMETA.JAVA 799
9.41 IMeta.java
IMeta.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Dec 31, 2008 */
package clojure.lang;
9.42 Indexed.java
(Counted [768])
Indexed.java
/*
\getchunk{Clojure Copyright}
*/
/* rich May 24, 2009 */
package clojure.lang;
9.43 IndexedSeq.java
(ISeq [805]) (Counted [768])
IndexedSeq.java
/*
\getchunk{Clojure Copyright}
800 CHAPTER 9. JVM/CLOJURE/LANG/
*/
package clojure.lang;
9.44 IObj.java
(IMeta [799])
IObj.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.45 IPersistentCollection.java
(Seqable [1140])
IPersistentCollection.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
int count();
IPersistentCollection empty();
9.46. IPERSISTENTLIST.JAVA 801
9.46 IPersistentList.java
(Sequential [1140]) (IPersistentStack [802])
IPersistentList.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.47 IPersistentMap.java
(Iterable [1723]) (Associative [576]) (Counted [768])
IPersistentMap.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
-
802 CHAPTER 9. JVM/CLOJURE/LANG/
9.48 IPersistentSet.java
(IPersistentCollection [800]) (Counted [768])
IPersistentSet.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
9.49 IPersistentStack.java
(IPersistentCollection [800])
IPersistentStack.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Sep 19, 2007 */
package clojure.lang;
IPersistentStack pop();
}
9.50 IPersistentVector.java
(Associative [576]) (Sequential [1140]) (Reversible [1094]) (Indexed [799]) (IPer-
sistentStack [802])
IPersistentVector.java
9.51. IPROMISEIMPL.JAVA 803
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.51 IPromiseImpl.java
IPromiseImpl.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.52 IProxy.java
IProxy.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Feb 27, 2008 */
package clojure.lang;
804 CHAPTER 9. JVM/CLOJURE/LANG/
9.53 IReduce.java
IReduce.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jun 11, 2008 */
package clojure.lang;
9.54 IReference.java
(IMeta [799])
IReference.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Dec 31, 2008 */
package clojure.lang;
9.55 IRef.java
(IDeref [773])
IRef.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Nov 18, 2007 */
package clojure.lang;
IFn getValidator();
IPersistentMap getWatches();
9.56 ISeq.java
(IPersistentCollection [800]) (Sequential [1140])
ISeq.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
/**
* A persistent, functional, sequence interface
* <p/>
* ISeqs are immutable values, i.e. neither first(), nor rest() changes
* or invalidates the ISeq
*/
806 CHAPTER 9. JVM/CLOJURE/LANG/
Object first();
ISeq next();
ISeq more();
9.57 IteratorSeq.java
(ASeq [571])
IteratorSeq.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.IOException;
import java.io.NotSerializableException;
import java.util.Iterator;
IteratorSeq(Iterator iter){
this.iter = iter;
state = new State();
this.state.val = state;
this.state._rest = state;
9.58. ITRANSIENTASSOCIATIVE.JAVA 807
9.58 ITransientAssociative.java
(ITransientCollection [808]) (ILookup [797])
ITransientAssociative.java
808 CHAPTER 9. JVM/CLOJURE/LANG/
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 17, 2009 */
package clojure.lang;
9.59 ITransientCollection.java
ITransientCollection.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 17, 2009 */
package clojure.lang;
IPersistentCollection persistent();
}
9.60 ITransientMap.java
(ITransientAssociative [807]) (Counted [768])
ITransientMap.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 17, 2009 */
9.61. ITRANSIENTSET.JAVA 809
package clojure.lang;
IPersistentMap persistent();
}
9.61 ITransientSet.java
(ITransientCollection [808]) (Counted [768])
ITransientSet.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
9.62 ITransientVector.java
(ITransientAssociative [807]) (Indexed [799])
ITransientVector.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 17, 2009 */
package clojure.lang;
ITransientVector pop();
}
9.63 Keyword.java
(IFn [774]) (Comparable [1723]) (Named [861]) (Serializable [1723])
Keyword.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 29, 2006 10:39:05 AM */
package clojure.lang;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
/**
* Indexer implements IFn for attr access
*
* @param obj - must be IPersistentMap
* @return the value at the key or nil if not found
* @throws Exception
*/
final public Object invoke(Object obj) throws Exception{
if(obj instanceof ILookup)
return ((ILookup)obj).valAt(this);
return RT.get(obj, this);
}
throws Exception{
return throwArity();
}
9.64 KeywordLookupSite.java
(ILookupSite [797]) (ILookupThunk [798])
KeywordLookupSite.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Nov 2, 2009 */
package clojure.lang;
final Keyword k;
9.65 LazilyPersistentVector.java
LazilyPersistentVector.java
/*
\getchunk{Clojure Copyright}
*/
/* rich May 14, 2008 */
package clojure.lang;
import java.util.Collection;
PersistentVector.EMPTY_NODE,items);
return PersistentVector.create(items);
}
9.66 LazySeq.java
(Obj [947]) (ISeq [805]) (List [1723])
LazySeq.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jan 31, 2009 */
package clojure.lang;
import java.util.*;
// java.util.Collection implementation
9.67 LineNumberingPushbackReader.java
(PushbackReader [1723])
LineNumberingPushbackReader.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.LineNumberReader;
import java.io.IOException;
// single \n.
}
}
9.68 LispReader.java
LispReader.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.math.BigInteger;
import java.math.BigDecimal;
import java.lang.*;
// Pattern.compile("[:]?([\\D&&[^:/]][^:/]*/)?[\\D&&[^:/]][^:/]*");
static Pattern symbolPat =
Pattern.compile("[:]?([\\D&&[^/]].*/)?([\\D&&[^/]][^/]*)");
//static Pattern varPat =
// Pattern.compile("([\\D&&[^:\\.]][^:\\.]*):([\\D&&[^:\\.]][^:\\.]*)");
//static Pattern intPat = Pattern.compile("[-+]?[0-9]+\\.?");
static Pattern intPat =
Pattern.compile(
"([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)"+
"|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?");
static Pattern ratioPat = Pattern.compile("([-+]?[0-9]+)/([0-9]+)");
static Pattern floatPat =
Pattern.compile("([-+]?[0-9]+(\\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?");
static final Symbol SLASH = Symbol.intern("/");
static final Symbol CLOJURE_SLASH = Symbol.intern("clojure.core","/");
//static Pattern accessorPat = Pattern.compile("\\.[a-zA-Z_]\\w*");
//static Pattern instanceMemberPat =
// Pattern.compile("\\.([a-zA-Z_][\\w\\.]*)\\.([a-zA-Z_]\\w*)");
//static Pattern staticMemberPat =
// Pattern.compile("([a-zA-Z_][\\w\\.]*)\\.([a-zA-Z_]\\w*)");
//static Pattern classNamePat =
// Pattern.compile("([a-zA-Z_][\\w\\.]*)\\.");
//symbol->gensymbol
static Var GENSYM_ENV = Var.create(null).setDynamic();
//sorted-map num->gensymbol
static Var ARG_ENV = Var.create(null).setDynamic();
static
{
\getchunk{LispReader Syntax Macro Table}
}
}
for(; ;)
{
int ch = r.read();
if(ch == -1 || isWhitespace(ch) || isTerminatingMacro(ch))
{
unread(r, ch);
return sb.toString();
}
sb.append((char) ch);
}
}
for(; ;)
{
int ch = r.read();
if(ch == -1 || isWhitespace(ch) || isMacro(ch))
{
unread(r, ch);
break;
}
sb.append((char) ch);
}
String s = sb.toString();
Object n = matchNumber(s);
if(n == null)
throw new NumberFormatException("Invalid number: " + s);
return n;
}
else if(s.equals("true"))
{
return RT.T;
}
else if(s.equals("false"))
{
return RT.F;
}
else if(s.equals("/"))
{
return SLASH;
}
else if(s.equals("clojure.core//"))
{
return CLOJURE_SLASH;
}
Object ret = null;
ret = matchSymbol(s);
if(ret != null)
return ret;
}
boolean isKeyword = s.charAt(0) == :;
Symbol sym = Symbol.intern(s.substring(isKeyword ? 1 : 0));
if(isKeyword)
return Keyword.intern(sym);
return sym;
}
return null;
}
m = ratioPat.matcher(s);
if(m.matches())
{
return
Numbers.divide(
Numbers.reduceBigInt(
BigInt.fromBigInteger(new BigInteger(m.group(1)))),
Numbers.reduceBigInt(
BigInt.fromBigInteger(new BigInteger(m.group(2)))));
}
return null;
}
" instead");
PushbackReader r = (PushbackReader) reader;
Object o = read(r, true, null, true);
return RT.list(sym, o);
}
/*
static class DerefReader extends AFn{
}
*/
{
String classname = s.ns;
String method = s.name;
return Reflector.invokeStaticMethod(classname, method, args);
}
else
{
return
Reflector.invokeConstructor(RT.classForName(s.name), args);
}
}
for(; ;)
{
int ch = r.read();
while(isWhitespace(ch))
9.68. LISPREADER.JAVA 835
ch = r.read();
if(ch == -1)
{
if(firstline < 0)
throw new Exception("EOF while reading");
else
throw new Exception(
"EOF while reading, starting at line " + firstline);
}
if(ch == delim)
break;
return a;
}
/*
public static void main(String[] args)
throws Exception{
//RT.init();
PushbackReader rdr =
new PushbackReader( new java.io.StringReader( "(+ 21 21)" ) );
Object input = LispReader.read(rdr, false, new Object(), false );
System.out.println(Compiler.eval(input));
}
9.69 LockingTransaction.java
LockingTransaction.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 26, 2007 */
package clojure.lang;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CountDownLatch;
@SuppressWarnings({"SynchronizeOnNonFinalField"})
public class LockingTransaction{
9.69. LOCKINGTRANSACTION.JAVA 837
void getReadPoint(){
readPoint = lastPoint.incrementAndGet();
}
long getCommitPoint(){
return lastPoint.incrementAndGet();
}
Info info;
long readPoint;
long startPoint;
long startTime;
final RetryEx retryex = new RetryEx();
final ArrayList<Agent.Action> actions = new ArrayList<Agent.Action>();
final HashMap<Ref, Object> vals = new HashMap<Ref, Object>();
final HashSet<Ref> sets = new HashSet<Ref>();
final TreeMap<Ref, ArrayList<CFn>> commutes =
new TreeMap<Ref, ArrayList<CFn>>();
}
catch(InterruptedException e)
{
throw retryex;
}
}
{
//ignore
}
throw retryex;
}
return null;
return t;
}
if(t.info != null)
return fn.call();
return t.run(fn);
}
: commutes.entrySet())
{
Ref ref = e.getKey();
if(sets.contains(ref)) continue;
if(ref.tvals == null)
{
ref.tvals =
new Ref.TVal(newval, commitPoint, msecs);
}
else if((ref.faults.get() > 0 &&
hcount < ref.maxHistory) ||
hcount < ref.minHistory)
{
ref.tvals =
new Ref.TVal(newval,
commitPoint,
msecs,
ref.tvals);
ref.faults.set(0);
}
else
{
ref.tvals = ref.tvals.next;
ref.tvals.val = newval;
ref.tvals.point = commitPoint;
ref.tvals.msecs = msecs;
}
if(ref.getWatches().count() > 0)
notify.add(new Notify(ref, oldval, newval));
}
done = true;
info.status.set(COMMITTED);
}
}
catch(RetryEx retry)
{
//eat this so we retry rather than fall out
}
finally
{
for(int k = locked.size() - 1; k >= 0; --k)
{
locked.get(k).lock.writeLock().unlock();
}
locked.clear();
844 CHAPTER 9. JVM/CLOJURE/LANG/
for(Ref r : ensures)
{
r.lock.readLock().unlock();
}
ensures.clear();
stop(done ? COMMITTED : RETRY);
try
{
if(done) //re-dispatch out of transaction
{
for(Notify n : notify)
{
n.ref.notifyWatches(n.oldval, n.newval);
}
for(Agent.Action action : actions)
{
Agent.dispatchAction(action);
}
}
}
finally
{
notify.clear();
actions.clear();
}
}
}
if(!done)
throw new Exception(
"Transaction failed after reaching retry limit");
return ret;
}
{
if(ver.point <= readPoint)
return ver.val;
} while((ver = ver.prior) != ref.tvals);
}
finally
{
ref.lock.readLock().unlock();
}
//no version of val precedes the read point
ref.faults.incrementAndGet();
throw retryex;
//writer exists
if(refinfo != null && refinfo.running())
{
ref.lock.readLock().unlock();
{
blockAndBail(refinfo);
}
}
else
ensures.add(ref);
}
/*
//for test
static CyclicBarrier barrier;
static ArrayList<Ref> items;
if(items == null)
{
ArrayList<Ref> temp = new ArrayList(nitems);
for(int i = 0; i < nitems; i++)
temp.add(new Ref(0));
items = temp;
}
}
}
}
}
}
}
ArrayList<Ref> si;
synchronized(items)
{
si = (ArrayList<Ref>) items.clone();
}
Collections.shuffle(si);
tasks.add(new Incrementer(niters, si));
//tasks.add(new Commuter(niters, si));
}
ExecutorService e = Executors.newFixedThreadPool(nthreads);
if(barrier == null)
barrier = new CyclicBarrier(ninstances);
System.out.println("waiting for other instances...");
barrier.await();
System.out.println("starting");
long start = System.nanoTime();
List<Future<Long>> results = e.invokeAll(tasks);
long estimatedTime = System.nanoTime() - start;
System.out.printf(
"nthreads: %d, nitems: %d, niters: %d, time: %d%n",
nthreads, nitems, niters, estimatedTime / 1000000);
e.shutdown();
for(Future<Long> result : results)
{
System.out.printf("%d, ", result.get() / 1000000);
}
System.out.println();
System.out.println("waiting for other instances...");
barrier.await();
synchronized(items)
{
for(Ref item : items)
{
System.out.printf("%d, ", (Integer) item.currentVal());
}
}
System.out.println("\ndone");
System.out.flush();
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
*/
}
-
850 CHAPTER 9. JVM/CLOJURE/LANG/
9.70 MapEntry.java
(AMapEntry [527])
MapEntry.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.util.Iterator;
9.71 MapEquivalence.java
MapEquivalence.java
/*
9.72. METHODIMPLCACHE.JAVA 851
\getchunk{Clojure Copyright}
*/
/* rich Aug 4, 2010 */
package clojure.lang;
//marker interface
public interface MapEquivalence{
}
9.72 MethodImplCache.java
MethodImplCache.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Nov 8, 2009 */
package clojure.lang;
9.73 MultiFn.java
(AFn [509])
MultiFn.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Sep 13, 2007 */
package clojure.lang;
import java.util.Map;
9.73. MULTIFN.JAVA 853
return this;
}
}
if(bestEntry == null)
return null;
//ensure basis has stayed stable throughout, else redo
if(cachedHierarchy == hierarchy.deref())
{
//place in cache
methodCache =
methodCache.assoc(dispatchVal, bestEntry.getValue());
return (IFn) bestEntry.getValue();
}
else
{
resetCache();
return findAndCacheBestMethod(dispatchVal);
}
}
throws Exception{
return getFn(
dispatchFn.invoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
arg9, arg10, arg11, arg12, arg13, arg14, arg15,
arg16, arg17, arg18))
.invoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9,
arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17,
arg18);
}
9.74 Named.java
Named.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Sep 20, 2007 */
package clojure.lang;
String getName();
}
9.75 Namespace.java
(AReference [552]) (Serializable [1723])
Namespace.java
862 CHAPTER 9. JVM/CLOJURE/LANG/
/*
\getchunk{Clojure Copyright}
*/
/* rich Jan 23, 2008 */
package clojure.lang;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
Namespace(Symbol name){
super(name.meta());
this.name = name;
mappings.set(RT.DEFAULT_IMPORTS);
aliases.set(RT.map());
}
}
IPersistentMap map = getMappings();
Object o;
Var v = null;
while((o = map.valAt(sym)) == null)
{
if(v == null)
v = new Var(this, sym);
IPersistentMap newMap = map.assoc(sym, v);
mappings.compareAndSet(map, newMap);
map = getMappings();
}
if(o instanceof Var && ((Var) o).ns == this)
return (Var) o;
if(v == null)
v = new Var(this, sym);
warnOrFailOnReplace(sym, o, v);
return v;
}
Object o;
while((o = map.valAt(sym)) == null)
{
IPersistentMap newMap = map.assoc(sym, val);
mappings.compareAndSet(map, newMap);
map = getMappings();
}
if(o == val)
return o;
warnOrFailOnReplace(sym, o, val);
return val;
{
throw new IllegalArgumentException(
"Cant unintern namespace-qualified symbol");
}
IPersistentMap map = getMappings();
while(map.containsKey(sym))
{
IPersistentMap newMap = map.without(sym);
mappings.compareAndSet(map, newMap);
map = getMappings();
}
}
return mappings.get().valAt(name);
}
9.76 Numbers.java
Numbers.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 31, 2008 */
package clojure.lang;
import java.math.BigInteger;
import java.math.BigDecimal;
import java.math.MathContext;
}
static interface BitOps{
BitOps combine(BitOps y);
return ops(x).incP((Number)x);
}
double q = n / d;
if(q <= Long.MAX_VALUE && q >= Long.MIN_VALUE)
{
return (double)(long) q;
}
else
{ //bigint quotient
return new BigDecimal(q).toBigInteger().doubleValue();
}
}
double q = n / d;
if(q <= Long.MAX_VALUE && q >= Long.MIN_VALUE)
{
return (n - ((long) q) * d);
}
else
{ //bigint quotient
Number bq = new BigDecimal(q).toBigInteger();
return (n - bq.doubleValue() * d);
}
}
BigInt bi = (BigInt) x;
if(bi.bipart == null)
return BigDecimal.valueOf(bi.lpart);
else
return new BigDecimal(bi.bipart);
}
else if(x instanceof BigInteger)
return new BigDecimal((BigInteger) x);
else if(x instanceof Double)
return new BigDecimal(((Number) x).doubleValue());
else if(x instanceof Float)
return new BigDecimal(((Number) x).doubleValue());
else if(x instanceof Ratio)
{
Ratio r = (Ratio)x;
return (BigDecimal)divide(new BigDecimal(r.numerator),
r.denominator);
}
else
return BigDecimal.valueOf(((Number) x).longValue());
}
BigInt.fromBigInteger(
bv.multiply(BigInteger.TEN.pow(-scale)));
else
return divide(bv, BigInteger.TEN.pow(scale));
}
return x;
}
n = n / gcd;
long d = val / gcd;
if(d == 1)
878 CHAPTER 9. JVM/CLOJURE/LANG/
return num(n);
if(d < 0)
{
n = -n;
d = -d;
}
return new Ratio(BigInteger.valueOf(n), BigInteger.valueOf(d));
}
Ratio r = (Ratio) x;
return new Ratio(r.numerator.negate(), r.denominator);
}
BigInteger bx = toBigInteger(x);
return BigInt.fromBigInteger(bx.add(BigInteger.ONE));
}
return mc == null
? ((BigDecimal) x).negate()
: ((BigDecimal) x).negate(mc);
}
return BigInt.fromBigInteger(toBigInteger(x).shiftLeft(n));
}
if(xc == Integer.class)
return LONG_OPS;
else if(xc == Double.class)
return DOUBLE_OPS;
else if(xc == Long.class)
return LONG_OPS;
else if(xc == Float.class)
return DOUBLE_OPS;
else if(xc == BigInt.class)
return BIGINT_OPS;
else if(xc == BigInteger.class)
return BIGINT_OPS;
else if(xc == Ratio.class)
return RATIO_OPS;
else if(xc == BigDecimal.class)
return BIGDECIMAL_OPS;
else
return LONG_OPS;
}
if(xc == Integer.class)
return Category.INTEGER;
else if(xc == Double.class)
return Category.FLOATING;
else if(xc == Long.class)
9.76. NUMBERS.JAVA 891
return Category.INTEGER;
else if(xc == Float.class)
return Category.FLOATING;
else if(xc == BigInt.class)
return Category.INTEGER;
else if(xc == Ratio.class)
return Category.RATIO;
else if(xc == BigDecimal.class)
return Category.DECIMAL;
else
return Category.INTEGER;
}
if(xc == Long.class)
return LONG_BITOPS;
else if(xc == Integer.class)
return LONG_BITOPS;
else if(xc == BigInt.class)
return BIGINT_BITOPS;
else if(xc == BigInteger.class)
return BIGINT_BITOPS;
else if(xc == Double.class ||
xc == Float.class ||
xc == BigDecimalOps.class ||
xc == Ratio.class)
throw new ArithmeticException(
"bit operation on non integer type: " + xc);
else
return LONG_BITOPS;
}
// return ~x;
//}
// if(x == Integer.MIN_VALUE)
// return throwIntOverflow();
// return x - 1;
//}
//}
/*
static public class F{
static public float add(float x, float y){
return x + y;
}
xs[i] -= y;
return xs;
}
return xs;
}
}
else
{
ISeq s = RT.seq(init);
for(int i = 0; i < size && s != null; i++, s = s.rest())
ret[i] = ((Number) s.first()).doubleValue();
}
return ret;
}
++highc;
xs[i] = high;
}
}
return RT.vector(xs, lowc, highc);
}
xs[i] -= ys[i];
return xs;
}
return x > 0;
}
ISeq s = RT.seq(sizeOrSeq);
int size = s.count();
int[] ret = new int[size];
for(int i = 0; i < size && s != null; i++, s = s.rest())
ret[i] = ((Number) s.first()).intValue();
return ret;
}
}
static public int[] vmap(IFn fn, int[] x, int[] ys) throws Exception{
int[] xs = x.clone();
for(int i = 0; i < xs.length; i++)
xs[i] = ((Number) fn.invoke(xs[i], ys[i])).intValue();
return xs;
}
xs[i] -= y;
return xs;
}
return xs;
}
return xs;
}
return xs;
}
}
*/
//overload resolution
//*
return addP(x,((Number)y).doubleValue());
}
return multiplyP(((Number)x).doubleValue(),y);
}
return x > y;
}
9.77 Obj.java
(IObj [800]) (Serializable [1723])
Obj.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 25, 2006 3:44:58 PM */
package clojure.lang;
import java.io.Serializable;
public Obj(){
_meta = null;
}
-
948 CHAPTER 9. JVM/CLOJURE/LANG/
9.78 PersistentArrayMap.java
(IObj [800]) (IEditableCollection [774])
PersistentArrayMap.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
/**
* Simple implementation of persistent map on an array
* <p/>
* Note that instances of this class are constant values
* i.e. add/remove etc return new values
* <p/>
* Copies array on every change, so only appropriate for
* _very_small_ maps
* <p/>
* null keys and values are ok, but you wont be able to distinguish
* a null value via valAt - use contains/entryAt
*/
protected PersistentArrayMap(){
this.array = new Object[]{};
9.78. PERSISTENTARRAYMAP.JAVA 949
this._meta = null;
}
}
}
//for iterator
Iter(Object[] array){
this(array, -2);
}
//for entryAt
Iter(Object[] array, int i){
this.array = array;
this.i = i;
}
int doCount() {
return len / 2;
}
IPersistentMap doPersistent(){
ensureEditable();
owner = null;
Object[] a = new Object[len];
System.arraycopy(array,0,a,0,len);
return new PersistentArrayMap(a);
}
void ensureEditable(){
if(owner == Thread.currentThread())
return;
if(owner != null)
throw new IllegalAccessError(
"Transient used by non-owner thread");
throw new IllegalAccessError(
"Transient used after persistent! call");
}
}
}
9.79 PersistentHashMap.java
(INode [58])
PersistentHashMap ArrayNode class
if(this.edit == edit)
return this;
return new ArrayNode(edit, count, this.array.clone());
}
}
}
(INode [58])
PersistentHashMap BitmapIndexedNode class
int bitmap;
Object[] array;
final AtomicReference<Thread> edit;
BitmapIndexedNode(AtomicReference<Thread> edit,
int bitmap, Object[] array){
this.bitmap = bitmap;
this.array = array;
this.edit = edit;
}
nodes[i] =
EMPTY.assoc(shift + 5,
Util.hash(array[j]),
array[j],
array[j+1],
addedLeaf);
j += 2;
}
return new ArrayNode(null, n + 1, nodes);
} else {
Object[] newArray = new Object[2*(n+1)];
System.arraycopy(array, 0, newArray, 0, 2*idx);
newArray[2*idx] = key;
addedLeaf.val = addedLeaf;
newArray[2*idx+1] = val;
System.arraycopy(array, 2*idx, newArray,
2*(idx+1), 2*(n-idx));
return
new BitmapIndexedNode(null, bitmap | bit, newArray);
}
}
}
return this;
}
private BitmapIndexedNode
ensureEditable(AtomicReference<Thread> edit){
if(this.edit == edit)
return this;
int n = Integer.bitCount(bitmap);
Object[] newArray =
new Object[n >= 0 ? 2*(n+1) : 4]; // make room for next assoc
System.arraycopy(array, 0, newArray, 0, 2*n);
return new BitmapIndexedNode(edit, bitmap, newArray);
}
private BitmapIndexedNode
editAndSet(AtomicReference<Thread> edit, int i, Object a) {
9.79. PERSISTENTHASHMAP.JAVA 963
private BitmapIndexedNode
editAndSet(AtomicReference<Thread> edit, int i,
Object a, int j, Object b) {
BitmapIndexedNode editable = ensureEditable(edit);
editable.array[i] = a;
editable.array[j] = b;
return editable;
}
private BitmapIndexedNode
editAndRemovePair(AtomicReference<Thread> edit,
int bit, int i) {
if (bitmap == bit)
return null;
BitmapIndexedNode editable = ensureEditable(edit);
editable.bitmap ^= bit;
System.arraycopy(editable.array, 2*(i+1), editable.array,
2*i, editable.array.length - 2*(i+1));
editable.array[editable.array.length - 2] = null;
editable.array[editable.array.length - 1] = null;
return editable;
}
return this;
return editAndSet(edit, 2*idx+1, val);
}
addedLeaf.val = addedLeaf;
return editAndSet(edit, 2*idx, null, 2*idx+1,
createNode(edit, shift + 5, keyOrNull,
valOrNode, hash, key, val));
} else {
int n = Integer.bitCount(bitmap);
if(n*2 < array.length) {
addedLeaf.val = addedLeaf;
BitmapIndexedNode editable = ensureEditable(edit);
System.arraycopy(editable.array, 2*idx,
editable.array, 2*(idx+1),
2*(n-idx));
editable.array[2*idx] = key;
editable.array[2*idx+1] = val;
editable.bitmap |= bit;
return editable;
}
if(n >= 16) {
INode[] nodes = new INode[32];
int jdx = mask(hash, shift);
nodes[jdx] = EMPTY.assoc(edit, shift + 5, hash, key,
val, addedLeaf);
int j = 0;
for(int i = 0; i < 32; i++)
if(((bitmap >>> i) & 1) != 0) {
if (array[j] == null)
nodes[i] = (INode) array[j+1];
else
nodes[i] =
EMPTY.assoc(edit,
shift + 5,
Util.hash(array[j]),
array[j],
array[j+1],
addedLeaf);
j += 2;
}
return new ArrayNode(edit, n + 1, nodes);
} else {
Object[] newArray = new Object[2*(n+4)];
System.arraycopy(array, 0, newArray, 0, 2*idx);
newArray[2*idx] = key;
addedLeaf.val = addedLeaf;
newArray[2*idx+1] = val;
System.arraycopy(array, 2*idx, newArray,
2*(idx+1), 2*(n-idx));
BitmapIndexedNode editable = ensureEditable(edit);
9.79. PERSISTENTHASHMAP.JAVA 965
editable.array = newArray;
editable.bitmap |= bit;
return editable;
}
}
}
(INode [58])
PersistentHashMap HashCollisionNode class
public Object find(int shift, int hash, Object key, Object notFound){
int idx = findIndex(key);
if(idx < 0)
return notFound;
if(Util.equiv(key, array[idx]))
return array[idx+1];
return notFound;
}
private HashCollisionNode
ensureEditable(AtomicReference<Thread> edit){
if(this.edit == edit)
return this;
return new HashCollisionNode(edit, hash, count, array);
}
private HashCollisionNode
ensureEditable(AtomicReference<Thread> edit, int count,
Object[] array){
if(this.edit == edit) {
this.array = array;
this.count = count;
return this;
}
return new HashCollisionNode(edit, hash, count, array);
}
private HashCollisionNode
editAndSet(AtomicReference<Thread> edit, int i, Object a) {
968 CHAPTER 9. JVM/CLOJURE/LANG/
private HashCollisionNode
editAndSet(AtomicReference<Thread> edit, int i, Object a,
int j, Object b) {
HashCollisionNode editable = ensureEditable(edit);
editable.array[i] = a;
editable.array[j] = b;
return editable;
}
if(idx == -1)
return this;
if(count == 1)
return null;
HashCollisionNode editable = ensureEditable(edit);
editable.array[idx] = editable.array[2*count-2];
editable.array[idx+1] = editable.array[2*count-1];
editable.array[2*count-2] = editable.array[2*count-1] = null;
editable.count--;
return editable;
}
}
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/*
A persistent rendition of Phil Bagwells Hash Array Mapped Trie
/*
* @param init {key1,val1,key2,val2,...}
*/
public static PersistentHashMap create(Object... init){
ITransientMap ret = EMPTY.asTransient();
for(int i = 0; i < init.length; i += 2)
{
ret = ret.assoc(init[i], init[i + 1]);
}
return (PersistentHashMap) ret.persistent();
}
/*
* @param init {key1,val1,key2,val2,...}
*/
public static PersistentHashMap create(IPersistentMap meta,
Object... init){
return create(init).withMeta(meta);
}
PersistentHashMap(int count,
INode root,
boolean hasNull,
Object nullValue){
this.count = count;
this.root = root;
this.hasNull = hasNull;
this.nullValue = nullValue;
this._meta = null;
}
TransientHashMap(PersistentHashMap m) {
this(new AtomicReference<Thread>(Thread.currentThread()),
m.root, m.count, m.hasNull, m.nullValue);
}
IPersistentMap doPersistent() {
edit.set(null);
return new PersistentHashMap(count, root, hasNull, nullValue);
}
int doCount() {
return count;
}
void ensureEditable(){
Thread owner = edit.get();
if(owner == Thread.currentThread())
return;
if(owner != null)
throw new IllegalAccessError(
"Transient used by non-owner thread");
throw new IllegalAccessError(
"Transient used after persistent! call");
}
}
/*
public static void main(String[] args){
try
{
ArrayList words = new ArrayList();
Scanner s = new Scanner(new File(args[0]));
976 CHAPTER 9. JVM/CLOJURE/LANG/
s.useDelimiter(Pattern.compile("\\W"));
while(s.hasNext())
{
String word = s.next();
words.add(word);
}
System.out.println("words: " + words.size());
IPersistentMap map = PersistentHashMap.EMPTY;
//IPersistentMap map = new PersistentTreeMap();
//Map ht = new Hashtable();
Map ht = new HashMap();
Random rand;
System.out.println("Building map");
long startTime = System.nanoTime();
for(Object word5 : words)
{
map = map.assoc(word5, word5);
}
rand = new Random(42);
IPersistentMap snapshotMap = map;
for(int i = 0; i < words.size() / 200; i++)
{
map = map.without(words.get(rand.nextInt(words.size() / 2)));
}
long estimatedTime = System.nanoTime() - startTime;
System.out.println("count = " + map.count() +
", time: " + estimatedTime / 1000000);
System.out.println("Building ht");
startTime = System.nanoTime();
for(Object word1 : words)
{
ht.put(word1, word1);
}
rand = new Random(42);
for(int i = 0; i < words.size() / 200; i++)
{
ht.remove(words.get(rand.nextInt(words.size() / 2)));
}
estimatedTime = System.nanoTime() - startTime;
System.out.println("count = " + ht.size() +
", time: " + estimatedTime / 1000000);
System.out.println("map lookup");
startTime = System.nanoTime();
int c = 0;
for(Object word2 : words)
{
if(!map.contains(word2))
9.79. PERSISTENTHASHMAP.JAVA 977
++c;
}
estimatedTime = System.nanoTime() - startTime;
System.out.println("notfound = " + c +
", time: " + estimatedTime / 1000000);
System.out.println("ht lookup");
startTime = System.nanoTime();
c = 0;
for(Object word3 : words)
{
if(!ht.containsKey(word3))
++c;
}
estimatedTime = System.nanoTime() - startTime;
System.out.println("notfound = " + c +
", time: " + estimatedTime / 1000000);
System.out.println("snapshotMap lookup");
startTime = System.nanoTime();
c = 0;
for(Object word4 : words)
{
if(!snapshotMap.contains(word4))
++c;
}
estimatedTime = System.nanoTime() - startTime;
System.out.println("notfound = " + c +
", time: " + estimatedTime / 1000000);
}
catch(FileNotFoundException e)
{
e.printStackTrace();
}
}
*/
final int i;
final ISeq s;
9.80 PersistentHashSet.java
(APersistentSet [538]) (IObj [800]) (IEditableCollection [774])
PersistentHashSet.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
import java.util.List;
{
ret = (PersistentHashSet) ret.cons(items.first());
}
return ret;
}
9.81 PersistentList.java
(ASeq [571]) (IPersistentList [801]) (IReduce [804]) (List [1723]) (Counted [768])
PersistentList.java
/*
\getchunk{Clojure Copyright}
9.81. PERSISTENTLIST.JAVA 983
*/
package clojure.lang;
import java.io.Serializable;
import java.util.*;
this._count = 1;
}
984 CHAPTER 9. JVM/CLOJURE/LANG/
PersistentList(IPersistentMap meta,
Object _first,
IPersistentList _rest,
int _count){
super(meta);
this._first = _first;
this._rest = _rest;
this._count = _count;
}
return EMPTY.withMeta(meta());
}
EmptyList(IPersistentMap meta){
super(meta);
}
return null;
}
if(objects.length > 0)
objects[0] = null;
return objects;
}
9.82 PersistentQueue.java
(Obj [947]) (IPersistentList [801]) (Collection [1723]) (Counted [768])
PersistentQueue.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
import java.util.Collection;
import java.util.Iterator;
//import java.util.concurrent.ConcurrentLinkedQueue;
/**
* conses onto rear, peeks/pops from front
* See Okasakis Batched Queues
* This differs in that it uses a PersistentVector as the rear,
* which is in-order,
* so no reversing or suspensions required for persistent use
*/
//*
final int cnt;
final ISeq f;
final PersistentVector r;
//static final int INITIAL_REAR_SIZE = 4;
int _hash = -1;
990 CHAPTER 9. JVM/CLOJURE/LANG/
PersistentQueue(IPersistentMap meta,
int cnt,
ISeq f,
PersistentVector r){
super(meta);
this.cnt = cnt;
this.f = f;
this.r = r;
}
// java.util.Collection implementation
/*
public static void main(String[] args){
if(args.length != 1)
{
System.err.println("Usage: PersistentQueue n");
return;
}
int n = Integer.parseInt(args[0]);
PersistentQueue q = PersistentQueue.EMPTY;
System.out.println("PersistentQueue");
startTime = System.nanoTime();
for(int i = 0; i < n; i++)
{
9.83. PERSISTENTSTRUCTMAP.JAVA 995
q = q.cons(i);
q = q.cons(i);
q = q.pop();
}
// IPersistentList lastq = null;
// IPersistentList lastq2;
for(int i = 0; i < n - 10; i++)
{
//lastq2 = lastq;
//lastq = q;
q = q.pop();
}
estimatedTime = System.nanoTime() - startTime;
System.out.println("time: " + estimatedTime / 1000000);
System.out.println("peek: " + q.peek());
IPersistentList q2 = q;
for(int i = 0; i < 10; i++)
{
q2 = (IPersistentList) q2.cons(i);
}
// for(ISeq s = q.seq();s != null;s = s.rest())
// System.out.println("q: " + s.first().toString());
// for(ISeq s = q2.seq();s != null;s = s.rest())
// System.out.println("q2: " + s.first().toString());
}
*/
}
9.83 PersistentStructMap.java
(APersistentMap [530]) (IObj [800])
PersistentStructMap.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Dec 16, 2007 */
package clojure.lang;
import java.util.Iterator;
import java.util.Map;
import java.io.Serializable;
/**
* Returns a new instance of PersistentStructMap using the given
* parameters. This function is used instead of the
* PersistentStructMap constructor by all methods that return a
* new PersistentStructMap. This is done so as to allow subclasses
* to return instances of their class from all PersistentStructMap
* methods.
*/
protected PersistentStructMap makeNew(IPersistentMap meta,
Def def,
998 CHAPTER 9. JVM/CLOJURE/LANG/
Object[] vals,
IPersistentMap ext){
return new PersistentStructMap(meta, def, vals, ext);
}
Object[] vals,
int i,
IPersistentMap ext){
super(meta);
this.i = i;
this.keys = keys;
this.vals = vals;
this.ext = ext;
}
9.84 PersistentTreeMap.java
(APersistentMap [530]) (IObj [800]) (Reversible [1094]) (Sorted [1141])
PersistentTreeMap.java
/*
\getchunk{Clojure Copyright}
*/
/* rich May 20, 2006 */
package clojure.lang;
import java.util.*;
/**
* Persistent Red Black Tree
* Note that instances of this class are constant values
* i.e. add/remove etc return new values
9.84. PERSISTENTTREEMAP.JAVA 1001
* <p/>
* See Okasaki, Kahrs, Larsen et al
*/
public PersistentTreeMap(){
this(RT.DEFAULT_COMPARATOR);
}
PersistentTreeMap(IPersistentMap meta,
Comparator comp,
Node tree,
int _count){
this._meta = meta;
this.comp = comp;
1002 CHAPTER 9. JVM/CLOJURE/LANG/
this.tree = tree;
this._count = _count;
}
if(_count > 0)
return Seq.create(tree, ascending, _count);
return null;
}
return depth(tree);
}
if(val == null)
return new Red(key);
return new RedVal(key, val);
}
int c = doCompare(key, t.key);
if(c == 0)
{
found.val = t;
return null;
}
Node ins = c < 0
? add(t.left(), key, val, found)
: add(t.right(), key, val, found);
if(ins == null) //found below
return null;
if(c < 0)
return t.addLeft(ins);
return t.addRight(ins);
}
return right;
else if(right == null)
return left;
else if(left instanceof Red)
{
if(right instanceof Red)
{
Node app = append(left.right(), right.left());
if(app instanceof Red)
return
red(app.key, app.val(),
red(left.key, left.val(),
left.left(), app.left()),
red(right.key, right.val(),
app.right(), right.right()));
else
return
red(left.key, left.val(), left.left(),
red(right.key, right.val(), app, right.right()));
}
else
return red(left.key, left.val(), left.left(),
append(left.right(), right));
}
else if(right instanceof Red)
return red(right.key, right.val(),
append(left, right.left()), right.right());
else //black/black
{
Node app = append(left.right(), right.left());
if(app instanceof Red)
return
red(app.key, app.val(),
black(left.key, left.val(),
left.left(), app.left()),
black(right.key, right.val(),
app.right(), right.right()));
else
return balanceLeftDel(left.key, left.val(), left.left(),
black(right.key, right.val(),
app, right.right()));
}
}
/*
static public void main(String args[]){
if(args.length != 1)
System.err.println("Usage: RBTree n");
1010 CHAPTER 9. JVM/CLOJURE/LANG/
int n = Integer.parseInt(args[0]);
Integer[] ints = new Integer[n];
for(int i = 0; i < ints.length; i++)
{
ints[i] = i;
}
Collections.shuffle(Arrays.asList(ints));
//force the ListMap class loading now
// try
// {
//
// //PersistentListMap.EMPTY.assocEx(1, null)
// .assocEx(2,null).assocEx(3,null);
// }
// catch(Exception e)
// {
// e.printStackTrace(); //To change body of catch statement
// //use File | Settings | File Templates.
// }
System.out.println("Building set");
//IPersistentMap set = new PersistentArrayMap();
//IPersistentMap set = new PersistentHashtableMap(1001);
IPersistentMap set = PersistentHashMap.EMPTY;
//IPersistentMap set = new ListMap();
//IPersistentMap set = new ArrayMap();
//IPersistentMap set = new PersistentTreeMap();
// for(int i = 0; i < ints.length; i++)
// {
// Integer anInt = ints[i];
// set = set.add(anInt);
// }
long startTime = System.nanoTime();
for(Integer anInt : ints)
{
set = set.assoc(anInt, anInt);
}
//System.out.println("_count = " + set.count());
System.out.println("Building ht");
Hashtable ht = new Hashtable(1001);
startTime = System.nanoTime();
// for(int i = 0; i < ints.length; i++)
// {
// Integer anInt = ints[i];
// ht.put(anInt,null);
// }
for(Integer anInt : ints)
{
ht.put(anInt, anInt);
}
//System.out.println("size = " + ht.size());
//Iterator it = ht.entrySet().iterator();
for(Object o1 : ht.entrySet())
{
Map.Entry o = (Map.Entry) o1;
if(!ht.containsKey(o.getKey()))
System.err.println("Cant find: " + o);
//else if(n < 2000)
// System.out.print(o.toString() + ",");
}
System.out.println("set lookup");
startTime = System.nanoTime();
1012 CHAPTER 9. JVM/CLOJURE/LANG/
int c = 0;
for(Integer anInt : ints)
{
if(!set.contains(anInt))
++c;
}
estimatedTime = System.nanoTime() - startTime;
System.out.println("notfound = " + c + ", time: " +
estimatedTime / 1000000);
System.out.println("ht lookup");
startTime = System.nanoTime();
c = 0;
for(Integer anInt : ints)
{
if(!ht.containsKey(anInt))
++c;
}
estimatedTime = System.nanoTime() - startTime;
System.out.println("notfound = " + c + ", time: " +
estimatedTime / 1000000);
9.85 PersistentTreeSet.java
(APersistentSet [538]) (IObj [800]) (Reversible [1094]) (Sorted [1141])
PersistentTreeSet.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
import java.util.Comparator;
9.86 PersistentVector.java
(APersistentVector [541]) (IObj [800]) (IEditableCollection [774])
PersistentVector.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 5, 2007 */
package clojure.lang;
import java.io.Serializable;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
Node(AtomicReference<Thread> edit){
this.edit = edit;
this.array = new Object[32];
}
}
return notFound;
}
return
new PersistentVector(meta(), cnt, shift,
doAssoc(shift, root, i, val), tail);
}
if(i == cnt)
return cons(val);
throw new IndexOutOfBoundsException();
}
private static Node doAssoc(int level, Node node, int i, Object val){
Node ret = new Node(node.edit,node.array.clone());
if(level == 0)
{
ret.array[i & 0x01f] = val;
}
else
{
int subidx = (i >>> level) & 0x01f;
ret.array[subidx] =
doAssoc(level - 5, (Node) node.array[subidx], i, val);
}
return ret;
}
pushTail(level-5,child, tailnode)
:newPath(root.edit,level-5, tailnode);
}
ret.array[subidx] = nodeToInsert;
return ret;
}
// newchild = tailNode;
// }
// else
// {
// newchild =
// pushTail(level - 5,
// (Object[]) arr[arr.length - 1], tailNode, expansion);
// if(expansion.val == null)
// {
// Object[] ret = arr.clone();
// ret[arr.length - 1] = newchild;
// return ret;
// }
// else
// newchild = expansion.val;
// }
// //expansion
// if(arr.length == 32)
// {
// expansion.val = new Object[]{newchild};
// return arr;
// }
// Object[] ret = new Object[arr.length + 1];
// System.arraycopy(arr, 0, ret, 0, arr.length);
// ret[arr.length] = newchild;
// expansion.val = null;
// return ret;
//}
TransientVector(PersistentVector v){
this(v.cnt, v.shift, editableRoot(v.root), editableTail(v.tail));
}
9.86. PERSISTENTVECTOR.JAVA 1023
void ensureEditable(){
Thread owner = root.edit.get();
if(owner == Thread.currentThread())
return;
if(owner != null)
throw new IllegalAccessError(
"Transient used by non-owner thread");
throw new IllegalAccessError(
"Transient used after persistent! call");
// root = editableRoot(root);
// tail = editableTail(tail);
}
{
int subidx = (i >>> level) & 0x01f;
ret.array[subidx] =
doAssoc(level - 5, (Node) node.array[subidx], i, val);
}
return ret;
}
Node newchild =
popTail(level - 5, (Node) node.array[subidx]);
if(newchild == null && subidx == 0)
return null;
else
{
Node ret = node;
ret.array[subidx] = newchild;
return ret;
}
}
else if(subidx == 0)
return null;
else
{
Node ret = node;
ret.array[subidx] = null;
return ret;
}
}
}
/*
static public void main(String[] args){
if(args.length != 3)
{
System.err.println("Usage: PersistentVector size writes reads");
return;
}
int size = Integer.parseInt(args[0]);
int writes = Integer.parseInt(args[1]);
int reads = Integer.parseInt(args[2]);
// Vector v = new Vector(size);
ArrayList v = new ArrayList(size);
// v.setSize(size);
//PersistentArray p = new PersistentArray(size);
PersistentVector p = PersistentVector.EMPTY;
// MutableVector mp = p.mutable();
Random rand;
long tv = 0;
System.out.println("ArrayList");
long startTime = System.nanoTime();
for(int i = 0; i < writes; i++)
{
v.set(rand.nextInt(size), i);
}
for(int i = 0; i < reads; i++)
{
tv += (Integer) v.get(rand.nextInt(size));
}
long estimatedTime = System.nanoTime() - startTime;
System.out.println("time: " + estimatedTime / 1000000);
System.out.println("PersistentVector");
rand = new Random(42);
startTime = System.nanoTime();
long tp = 0;
// PersistentVector oldp = p;
//Random rand2 = new Random(42);
MutableVector mp = p.mutable();
for(int i = 0; i < writes; i++)
{
// p = p.assocN(rand.nextInt(size), i);
mp = mp.assocN(rand.nextInt(size), i);
// mp = mp.assoc(rand.nextInt(size), i);
//dummy set to force perverse branching
//oldp = oldp.assocN(rand2.nextInt(size), i);
}
for(int i = 0; i < reads; i++)
{
// tp += (Integer) p.nth(rand.nextInt(size));
tp += (Integer) mp.nth(rand.nextInt(size));
}
// p = mp.immutable();
//mp.cons(42);
estimatedTime = System.nanoTime() - startTime;
System.out.println("time: " + estimatedTime / 1000000);
for(int i = 0; i < size / 2; i++)
{
mp = mp.pop();
// p = p.pop();
v.remove(v.size() - 1);
}
p = (PersistentVector) mp.immutable();
//mp.pop(); //should fail
for(int i = 0; i < size / 2; i++)
{
tp += (Integer) p.nth(i);
1030 CHAPTER 9. JVM/CLOJURE/LANG/
tv += (Integer) v.get(i);
}
System.out.println("Done: " + tv + ", " + tp);
}
// */
}
9.87 ProxyHandler.java
(InvocationHandler [1723])
ProxyHandler.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Oct 4, 2007 */
package clojure.lang;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
return System.identityHashCode(proxy);
}
else if(method.getName().equals("toString"))
{
return "Proxy: " + System.identityHashCode(proxy);
}
throw new UnsupportedOperationException();
}
Object ret = fn.applyTo(ArraySeq.create(args));
if(rt == Void.TYPE)
return null;
else if(rt.isPrimitive())
{
if(rt == Character.TYPE)
return ret;
else if(rt == Integer.TYPE)
return ((Number) ret).intValue();
else if(rt == Long.TYPE)
return ((Number) ret).longValue();
else if(rt == Float.TYPE)
return ((Number) ret).floatValue();
else if(rt == Double.TYPE)
return ((Number) ret).doubleValue();
else if(rt == Boolean.TYPE && !(ret instanceof Boolean))
return ret == null ? Boolean.FALSE : Boolean.TRUE;
else if(rt == Byte.TYPE)
return (byte) ((Number) ret).intValue();
else if(rt == Short.TYPE)
return (short) ((Number) ret).intValue();
}
return ret;
}
}
9.88 Range.java
(ASeq [571]) (IReduce [804]) (Counted [768])
Range.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Apr 1, 2008 */
package clojure.lang;
1032 CHAPTER 9. JVM/CLOJURE/LANG/
}
9.89. RATIO.JAVA 1033
9.89 Ratio.java
(Number [1723]) (Comparable [1723])
Ratio.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 31, 2008 */
package clojure.lang;
import java.math.BigInteger;
import java.math.BigDecimal;
import java.math.MathContext;
9.90 Ref.java
(ARef [553]) (IFn [774]) (Comparable [1723]) (IRef [805])
Ref.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 25, 2007 */
package clojure.lang;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
9.90. REF.JAVA 1035
this.next = this;
this.prior = this;
}
TVal tvals;
final AtomicInteger faults;
final ReentrantReadWriteLock lock;
LockingTransaction.Info tinfo;
//IFn validator;
final long id;
// ok out of transaction
Object currentVal(){
try
{
lock.readLock().lock();
if(tvals != null)
return tvals.val;
throw new IllegalStateException(
this.toString() + " is unbound.");
}
finally
{
lock.readLock().unlock();
}
}
//*
9.90. REF.JAVA 1037
//*/
boolean isBound(){
try
{
lock.readLock().lock();
return tvals != null;
}
finally
{
lock.readLock().unlock();
}
}
finally
{
lock.writeLock().unlock();
}
}
int histCount(){
if(tvals == null)
return 0;
else
{
int count = 0;
for(TVal tv = tvals.next;tv != tvals;tv = tv.next)
count++;
return count;
}
}
Object arg10)
throws Exception{
return
fn().invoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9,
arg10);
}
-
1044 CHAPTER 9. JVM/CLOJURE/LANG/
9.91 Reflector.java
Reflector.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Apr 19, 2006 */
package clojure.lang;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Arrays;
Method m = null;
Object[] boxedArgs = null;
if(methods.isEmpty())
{
throw new IllegalArgumentException(
noMethodReport(methodName,target));
}
else if(methods.size() == 1)
{
m = (Method) methods.get(0);
boxedArgs = boxArgs(m.getParameterTypes(), args);
}
else //overloaded w/same arity
{
Method foundm = null;
for(Iterator i = methods.iterator(); i.hasNext();)
{
m = (Method) i.next();
if(!Modifier.isPublic(m.getDeclaringClass().getModifiers()))
{
//public method of non-public class, try to find it in
//hierarchy
Method oldm = m;
m = getAsMethodOfPublicBase(m.getDeclaringClass(), m);
if(m == null)
throw new IllegalArgumentException(
"Cant call public method of non-public class: " +
oldm.toString());
}
try
{
1046 CHAPTER 9. JVM/CLOJURE/LANG/
{
Constructor ctor = allctors[i];
if(ctor.getParameterTypes().length == args.length)
ctors.add(ctor);
}
if(ctors.isEmpty())
{
throw new IllegalArgumentException("No matching ctor found"
+ " for " + c);
}
else if(ctors.size() == 1)
{
Constructor ctor = (Constructor) ctors.get(0);
return
ctor.newInstance(
boxArgs(ctor.getParameterTypes(), args));
}
else //overloaded w/same arity
{
for(Iterator iterator = ctors.iterator();
iterator.hasNext();)
{
Constructor ctor = (Constructor) iterator.next();
Class[] params = ctor.getParameterTypes();
if(isCongruent(params, args))
{
Object[] boxedArgs = boxArgs(params, args);
return ctor.newInstance(boxedArgs);
}
}
throw new IllegalArgumentException("No matching ctor found"
+ " for " + c);
}
}
catch(InvocationTargetException e)
{
if(e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else if(e.getCause() instanceof Error)
throw (Error) e.getCause();
throw e;
}
}
}
throw new IllegalArgumentException(
"No matching field found: " + fieldName
+ " for " + target.getClass());
}
// .equals(method))))
// {
// methods.add(allmethods[i]);
// }
}
if(methods.isEmpty())
methods.addAll(bridgeMethods);
9.92 Repl.java
Repl.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Oct 18, 2007 */
package clojure.lang;
import clojure.main;
9.93. RESTFN.JAVA 1055
9.93 RestFn.java
(AFunction [519])
RestFn.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
return null;
}
throws Exception{
return null;
}
switch(getRequiredArity())
{
case 0:
return doInvoke(Util.ret1(args,args = null));
case 1:
return doInvoke(args.first()
, Util.ret1(args.next(),args=null));
case 2:
return doInvoke(args.first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 3:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 4:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 5:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 6:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 7:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 8:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
1060 CHAPTER 9. JVM/CLOJURE/LANG/
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 9:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 10:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 11:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 12:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
9.93. RESTFN.JAVA 1061
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 13:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 14:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 15:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
1062 CHAPTER 9. JVM/CLOJURE/LANG/
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 16:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 17:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 18:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
9.93. RESTFN.JAVA 1063
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 19:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
case 20:
return doInvoke(args.first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
1064 CHAPTER 9. JVM/CLOJURE/LANG/
, (args = args.next()).first()
, (args = args.next()).first()
, (args = args.next()).first()
, Util.ret1(args.next(),args=null));
}
return throwArity(-1);
}
return throwArity(2);
}
case 0:
return doInvoke(ArraySeq.create(arg1, arg2, arg3, arg4,
arg5, arg6, arg7));
case 1:
return doInvoke(arg1,
ArraySeq.create(arg2, arg3, arg4, arg5,
arg6, arg7));
case 2:
return doInvoke(arg1, arg2,
ArraySeq.create(arg3, arg4, arg5, arg6,
arg7));
case 3:
return doInvoke(arg1, arg2, arg3,
ArraySeq.create(arg4, arg5, arg6, arg7));
case 4:
return doInvoke(arg1, arg2, arg3, arg4,
ArraySeq.create(arg5, arg6, arg7));
case 5:
return doInvoke(arg1, arg2, arg3, arg4, arg5,
ArraySeq.create(arg6, arg7));
case 6:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6,
ArraySeq.create(arg7));
case 7:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
null);
default:
return throwArity(7);
}
case 6:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6,
ArraySeq.create(arg7, arg8, arg9, arg10));
case 7:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
ArraySeq.create(arg8, arg9, arg10));
case 8:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, ArraySeq.create(arg9, arg10));
case 9:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, ArraySeq.create(arg10));
case 10:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, null);
default:
return throwArity(10);
}
{
case 0:
return doInvoke(ArraySeq.create(arg1, arg2, arg3, arg4,
arg5, arg6, arg7, arg8, arg9, arg10,
arg11, arg12, arg13));
case 1:
return doInvoke(arg1,
ArraySeq.create(arg2, arg3, arg4, arg5,
arg6, arg7, arg8, arg9, arg10, arg11,
arg12, arg13));
case 2:
return doInvoke(arg1, arg2,
ArraySeq.create(arg3, arg4, arg5, arg6,
arg7, arg8, arg9, arg10, arg11, arg12,
arg13));
case 3:
return doInvoke(arg1, arg2, arg3,
ArraySeq.create(arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
arg13));
case 4:
return doInvoke(arg1, arg2, arg3, arg4,
ArraySeq.create(arg5, arg6, arg7, arg8,
arg9, arg10, arg11, arg12, arg13));
case 5:
return doInvoke(arg1, arg2, arg3, arg4, arg5,
ArraySeq.create(arg6, arg7, arg8, arg9,
arg10, arg11, arg12, arg13));
case 6:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6,
ArraySeq.create(arg7, arg8, arg9, arg10,
arg11, arg12, arg13));
case 7:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
ArraySeq.create(arg8, arg9, arg10, arg11,
arg12, arg13));
case 8:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8,
ArraySeq.create(arg9, arg10, arg11,
arg12, arg13));
case 9:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9,
ArraySeq.create(arg10, arg11, arg12,
arg13));
case 10:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10,
ArraySeq.create(arg11, arg12, arg13));
1076 CHAPTER 9. JVM/CLOJURE/LANG/
case 11:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11,
ArraySeq.create(arg12, arg13));
case 12:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
ArraySeq.create(arg13));
case 13:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
null);
default:
return throwArity(13);
}
case 5:
return doInvoke(arg1, arg2, arg3, arg4, arg5,
ArraySeq.create(arg6, arg7, arg8, arg9,
arg10, arg11, arg12, arg13, arg14));
case 6:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6,
ArraySeq.create(arg7, arg8, arg9, arg10,
arg11, arg12, arg13, arg14));
case 7:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
ArraySeq.create(arg8, arg9, arg10, arg11,
arg12, arg13, arg14));
case 8:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8,
ArraySeq.create(arg9, arg10, arg11,
arg12, arg13, arg14));
case 9:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9,
ArraySeq.create(arg10, arg11, arg12,
arg13, arg14));
case 10:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10,
ArraySeq.create(arg11, arg12, arg13,
arg14));
case 11:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11,
ArraySeq.create(arg12, arg13, arg14));
case 12:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
ArraySeq.create(arg13, arg14));
case 13:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
ArraySeq.create(arg14));
case 14:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, null);
default:
return throwArity(14);
}
arg16));
case 1:
return doInvoke(arg1,
ArraySeq.create(arg2, arg3, arg4, arg5,
arg6, arg7, arg8, arg9, arg10, arg11,
arg12, arg13, arg14, arg15, arg16));
case 2:
return doInvoke(arg1, arg2,
ArraySeq.create(arg3, arg4, arg5, arg6,
arg7, arg8, arg9, arg10, arg11, arg12,
arg13, arg14, arg15, arg16));
case 3:
return doInvoke(arg1, arg2, arg3,
ArraySeq.create(arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
arg13, arg14, arg15, arg16));
case 4:
return doInvoke(arg1, arg2, arg3, arg4,
ArraySeq.create(arg5, arg6, arg7, arg8,
arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16));
case 5:
return doInvoke(arg1, arg2, arg3, arg4, arg5,
ArraySeq.create(arg6, arg7, arg8, arg9,
arg10, arg11, arg12, arg13, arg14,
arg15, arg16));
case 6:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6,
ArraySeq.create(arg7, arg8, arg9, arg10,
arg11, arg12, arg13, arg14, arg15,
arg16));
case 7:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
ArraySeq.create(arg8, arg9, arg10, arg11,
arg12, arg13, arg14, arg15, arg16));
case 8:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8,
ArraySeq.create(arg9, arg10, arg11,
arg12, arg13, arg14, arg15, arg16));
case 9:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9,
ArraySeq.create(arg10, arg11, arg12,
arg13, arg14, arg15, arg16));
case 10:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10,
ArraySeq.create(arg11, arg12, arg13,
arg14, arg15, arg16));
9.93. RESTFN.JAVA 1081
case 11:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11,
ArraySeq.create(arg12, arg13, arg14,
arg15, arg16));
case 12:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
ArraySeq.create(arg13, arg14, arg15,
arg16));
case 13:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
ArraySeq.create(arg14, arg15, arg16));
case 14:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14,
ArraySeq.create(arg15, arg16));
case 15:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15,
ArraySeq.create(arg16));
case 16:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16, null);
default:
return throwArity(16);
}
case 11:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11,
ArraySeq.create(arg12, arg13, arg14,
arg15, arg16, arg17));
case 12:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
ArraySeq.create(arg13, arg14, arg15,
arg16, arg17));
case 13:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
ArraySeq.create(arg14, arg15, arg16,
arg17));
case 14:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14,
ArraySeq.create(arg15, arg16, arg17));
case 15:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15,
ArraySeq.create(arg16, arg17));
case 16:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16,
ArraySeq.create(arg17));
case 17:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16, arg17, null);
default:
return throwArity(17);
}
arg19, arg20));
case 11:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11,
ArraySeq.create(arg12, arg13, arg14,
arg15, arg16, arg17, arg18, arg19, arg20));
case 12:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
ArraySeq.create(arg13, arg14, arg15,
arg16, arg17, arg18, arg19, arg20));
case 13:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
ArraySeq.create(arg14, arg15, arg16,
arg17, arg18, arg19, arg20));
case 14:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14,
ArraySeq.create(arg15, arg16, arg17,
arg18, arg19, arg20));
case 15:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15,
ArraySeq.create(arg16, arg17, arg18,
arg19, arg20));
case 16:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16,
ArraySeq.create(arg17, arg18, arg19,
arg20));
case 17:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16, arg17,
ArraySeq.create(arg18, arg19, arg20));
case 18:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16, arg17, arg18,
ArraySeq.create(arg19, arg20));
case 19:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
arg14, arg15, arg16, arg17, arg18, arg19,
ArraySeq.create(arg20));
case 20:
9.93. RESTFN.JAVA 1091
case 5:
return doInvoke(arg1, arg2, arg3, arg4, arg5,
ontoArrayPrepend(args, arg6, arg7, arg8,
arg9, arg10, arg11, arg12, arg13, arg14,
arg15, arg16, arg17, arg18, arg19,
arg20));
case 6:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6,
ontoArrayPrepend(args, arg7, arg8, arg9,
arg10, arg11, arg12, arg13, arg14, arg15,
arg16, arg17, arg18, arg19, arg20));
case 7:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
ontoArrayPrepend(args, arg8, arg9, arg10,
arg11, arg12, arg13, arg14, arg15, arg16,
arg17, arg18, arg19, arg20));
case 8:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8,
ontoArrayPrepend(args, arg9, arg10, arg11,
arg12, arg13, arg14, arg15, arg16, arg17,
arg18, arg19, arg20));
case 9:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9,
ontoArrayPrepend(args, arg10, arg11, arg12,
arg13, arg14, arg15, arg16, arg17, arg18,
arg19, arg20));
case 10:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10,
ontoArrayPrepend(args, arg11, arg12, arg13,
arg14, arg15, arg16, arg17, arg18, arg19,
arg20));
case 11:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11,
ontoArrayPrepend(args, arg12, arg13, arg14,
arg15, arg16, arg17, arg18, arg19, arg20));
case 12:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12,
ontoArrayPrepend(args, arg13, arg14, arg15,
arg16, arg17, arg18, arg19, arg20));
case 13:
return doInvoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7,
arg8, arg9, arg10, arg11, arg12, arg13,
ontoArrayPrepend(args, arg14, arg15, arg16,
arg17, arg18, arg19, arg20));
case 14:
9.93. RESTFN.JAVA 1093
return ret;
}
9.94 Reversible.java
Reversible.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jan 5, 2008 */
package clojure.lang;
9.95 RT.java
RT.java
/*
\getchunk{Clojure Copyright}
*/
9.95. RT.JAVA 1095
package clojure.lang;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Callable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.*;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.net.URL;
import java.net.JarURLConnection;
import java.nio.charset.Charset;
//simple-symbol->class
final static IPersistentMap DEFAULT_IMPORTS = map(
// Symbol.intern("RT"), "clojure.lang.RT",
// Symbol.intern("Num"), "clojure.lang.Num",
// Symbol.intern("Symbol"), "clojure.lang.Symbol",
// Symbol.intern("Keyword"), "clojure.lang.Keyword",
// Symbol.intern("Var"), "clojure.lang.Var",
// Symbol.intern("Ref"), "clojure.lang.Ref",
// Symbol.intern("IFn"), "clojure.lang.IFn",
// Symbol.intern("IObj"), "clojure.lang.IObj",
// Symbol.intern("ISeq"), "clojure.lang.ISeq",
// Symbol.intern("IPersistentCollection"),
// "clojure.lang.IPersistentCollection",
// Symbol.intern("IPersistentMap"), "clojure.lang.IPersistentMap",
// Symbol.intern("IPersistentList"), "clojure.lang.IPersistentList",
// Symbol.intern("IPersistentVector"), "clojure.lang.IPersistentVector",
Symbol.intern("Boolean"), Boolean.class,
Symbol.intern("Byte"), Byte.class,
Symbol.intern("Character"), Character.class,
Symbol.intern("Class"), Class.class,
Symbol.intern("ClassLoader"), ClassLoader.class,
Symbol.intern("Compiler"), Compiler.class,
Symbol.intern("Double"), Double.class,
1096 CHAPTER 9. JVM/CLOJURE/LANG/
Symbol.intern("Enum"), Enum.class,
Symbol.intern("Float"), Float.class,
Symbol.intern("InheritableThreadLocal"), InheritableThreadLocal.class,
Symbol.intern("Integer"), Integer.class,
Symbol.intern("Long"), Long.class,
Symbol.intern("Math"), Math.class,
Symbol.intern("Number"), Number.class,
Symbol.intern("Object"), Object.class,
Symbol.intern("Package"), Package.class,
Symbol.intern("Process"), Process.class,
Symbol.intern("ProcessBuilder"), ProcessBuilder.class,
Symbol.intern("Runtime"), Runtime.class,
Symbol.intern("RuntimePermission"), RuntimePermission.class,
Symbol.intern("SecurityManager"), SecurityManager.class,
Symbol.intern("Short"), Short.class,
Symbol.intern("StackTraceElement"), StackTraceElement.class,
Symbol.intern("StrictMath"), StrictMath.class,
Symbol.intern("String"), String.class,
Symbol.intern("StringBuffer"), StringBuffer.class,
Symbol.intern("StringBuilder"), StringBuilder.class,
Symbol.intern("System"), System.class,
Symbol.intern("Thread"), Thread.class,
Symbol.intern("ThreadGroup"), ThreadGroup.class,
Symbol.intern("ThreadLocal"), ThreadLocal.class,
Symbol.intern("Throwable"), Throwable.class,
Symbol.intern("Void"), Void.class,
Symbol.intern("Appendable"), Appendable.class,
Symbol.intern("CharSequence"), CharSequence.class,
Symbol.intern("Cloneable"), Cloneable.class,
Symbol.intern("Comparable"), Comparable.class,
Symbol.intern("Iterable"), Iterable.class,
Symbol.intern("Readable"), Readable.class,
Symbol.intern("Runnable"), Runnable.class,
Symbol.intern("Callable"), Callable.class,
Symbol.intern("BigInteger"), BigInteger.class,
Symbol.intern("BigDecimal"), BigDecimal.class,
Symbol.intern("ArithmeticException"), ArithmeticException.class,
Symbol.intern("ArrayIndexOutOfBoundsException"),
ArrayIndexOutOfBoundsException.class,
Symbol.intern("ArrayStoreException"), ArrayStoreException.class,
Symbol.intern("ClassCastException"), ClassCastException.class,
Symbol.intern("ClassNotFoundException"), ClassNotFoundException.class,
Symbol.intern("CloneNotSupportedException"),
CloneNotSupportedException.class,
Symbol.intern("EnumConstantNotPresentException"),
EnumConstantNotPresentException.class,
Symbol.intern("Exception"), Exception.class,
Symbol.intern("IllegalAccessException"), IllegalAccessException.class,
Symbol.intern("IllegalArgumentException"),
IllegalArgumentException.class,
9.95. RT.JAVA 1097
Symbol.intern("IllegalMonitorStateException"),
IllegalMonitorStateException.class,
Symbol.intern("IllegalStateException"), IllegalStateException.class,
Symbol.intern("IllegalThreadStateException"),
IllegalThreadStateException.class,
Symbol.intern("IndexOutOfBoundsException"),
IndexOutOfBoundsException.class,
Symbol.intern("InstantiationException"), InstantiationException.class,
Symbol.intern("InterruptedException"), InterruptedException.class,
Symbol.intern("NegativeArraySizeException"),
NegativeArraySizeException.class,
Symbol.intern("NoSuchFieldException"), NoSuchFieldException.class,
Symbol.intern("NoSuchMethodException"), NoSuchMethodException.class,
Symbol.intern("NullPointerException"), NullPointerException.class,
Symbol.intern("NumberFormatException"), NumberFormatException.class,
Symbol.intern("RuntimeException"), RuntimeException.class,
Symbol.intern("SecurityException"), SecurityException.class,
Symbol.intern("StringIndexOutOfBoundsException"),
StringIndexOutOfBoundsException.class,
Symbol.intern("TypeNotPresentException"),
TypeNotPresentException.class,
Symbol.intern("UnsupportedOperationException"),
UnsupportedOperationException.class,
Symbol.intern("AbstractMethodError"), AbstractMethodError.class,
Symbol.intern("AssertionError"), AssertionError.class,
Symbol.intern("ClassCircularityError"), ClassCircularityError.class,
Symbol.intern("ClassFormatError"), ClassFormatError.class,
Symbol.intern("Error"), Error.class,
Symbol.intern("ExceptionInInitializerError"),
ExceptionInInitializerError.class,
Symbol.intern("IllegalAccessError"), IllegalAccessError.class,
Symbol.intern("IncompatibleClassChangeError"),
IncompatibleClassChangeError.class,
Symbol.intern("InstantiationError"), InstantiationError.class,
Symbol.intern("InternalError"), InternalError.class,
Symbol.intern("LinkageError"), LinkageError.class,
Symbol.intern("NoClassDefFoundError"), NoClassDefFoundError.class,
Symbol.intern("NoSuchFieldError"), NoSuchFieldError.class,
Symbol.intern("NoSuchMethodError"), NoSuchMethodError.class,
Symbol.intern("OutOfMemoryError"), OutOfMemoryError.class,
Symbol.intern("StackOverflowError"), StackOverflowError.class,
Symbol.intern("ThreadDeath"), ThreadDeath.class,
Symbol.intern("UnknownError"), UnknownError.class,
Symbol.intern("UnsatisfiedLinkError"), UnsatisfiedLinkError.class,
Symbol.intern("UnsupportedClassVersionError"),
UnsupportedClassVersionError.class,
Symbol.intern("VerifyError"), VerifyError.class,
Symbol.intern("VirtualMachineError"), VirtualMachineError.class,
Symbol.intern("Thread$UncaughtExceptionHandler"),
Thread.UncaughtExceptionHandler.class,
1098 CHAPTER 9. JVM/CLOJURE/LANG/
Symbol.intern("Thread$State"), Thread.State.class,
Symbol.intern("Deprecated"), Deprecated.class,
Symbol.intern("Override"), Override.class,
Symbol.intern("SuppressWarnings"), SuppressWarnings.class
// Symbol.intern("Collection"), "java.util.Collection",
// Symbol.intern("Comparator"), "java.util.Comparator",
// Symbol.intern("Enumeration"), "java.util.Enumeration",
// Symbol.intern("EventListener"), "java.util.EventListener",
// Symbol.intern("Formattable"), "java.util.Formattable",
// Symbol.intern("Iterator"), "java.util.Iterator",
// Symbol.intern("List"), "java.util.List",
// Symbol.intern("ListIterator"), "java.util.ListIterator",
// Symbol.intern("Map"), "java.util.Map",
// Symbol.intern("Map$Entry"), "java.util.Map$Entry",
// Symbol.intern("Observer"), "java.util.Observer",
// Symbol.intern("Queue"), "java.util.Queue",
// Symbol.intern("RandomAccess"), "java.util.RandomAccess",
// Symbol.intern("Set"), "java.util.Set",
// Symbol.intern("SortedMap"), "java.util.SortedMap",
// Symbol.intern("SortedSet"), "java.util.SortedSet"
);
Var.intern(CLOJURE_NS, Symbol.intern("*read-eval*"), T)
.setDynamic();
final static public Var ASSERT =
Var.intern(CLOJURE_NS, Symbol.intern("*assert*"), T)
.setDynamic();
final static public Var MATH_CONTEXT =
Var.intern(CLOJURE_NS, Symbol.intern("*math-context*"), null)
.setDynamic();
static Keyword LINE_KEY = Keyword.intern(null, "line");
static Keyword FILE_KEY = Keyword.intern(null, "file");
static Keyword DECLARED_KEY = Keyword.intern(null, "declared");
static Keyword DOC_KEY = Keyword.intern(null, "doc");
final static public Var USE_CONTEXT_CLASSLOADER =
Var.intern(CLOJURE_NS,
Symbol.intern("*use-context-classloader*"), T)
.setDynamic();
//final static public Var CURRENT_MODULE =
// Var.intern(Symbol.intern("clojure.core", "current-module"),
// Module.findOrCreateModule("clojure/user"));
Symbol.intern("*allow-unresolved-vars*"), F)
.setDynamic();
} else {
return new PrintWriter(w);
}
}
static{
Keyword arglistskw = Keyword.intern(null, "arglists");
Symbol namesym = Symbol.intern("name");
OUT.setTag(Symbol.intern("java.io.Writer"));
CURRENT_NS.setTag(Symbol.intern("clojure.lang.Namespace"));
AGENT.setMeta(map(DOC_KEY,
"The agent currently running an action on "+
"this thread, else nil"));
AGENT.setTag(Symbol.intern("clojure.lang.Agent"));
MATH_CONTEXT.setTag(Symbol.intern("java.math.MathContext"));
Var nv = Var.intern(CLOJURE_NS, NAMESPACE, bootNamespace);
nv.setMacro();
Var v;
v = Var.intern(CLOJURE_NS, IN_NAMESPACE, inNamespace);
v.setMeta(map(DOC_KEY,
1102 CHAPTER 9. JVM/CLOJURE/LANG/
try {
Compiler.compile(
new InputStreamReader(ins, UTF8), cljfile,
cljfile.substring(1 + cljfile.lastIndexOf("/")));
}
finally {
ins.close();
}
}
else
throw new FileNotFoundException(
"Could not locate Clojure resource on classpath: " + cljfile);
}
Var.pushThreadBindings(
RT.map(CURRENT_NS, CURRENT_NS.deref(),
WARN_ON_REFLECTION, WARN_ON_REFLECTION.deref()));
try {
Symbol USER = Symbol.intern("user");
Symbol CLOJURE = Symbol.intern("clojure.core");
else if(coll.getClass().isArray())
return ArraySeq.createFromObject(coll);
else if(coll instanceof CharSequence)
return StringSeq.create((CharSequence) coll);
else if(coll instanceof Map)
return seq(((Map) coll).entrySet());
else {
Class c = coll.getClass();
Class sc = c.getSuperclass();
throw new IllegalArgumentException(
"Dont know how to create ISeq from: " + c.getName());
}
}
return null;
}
throws Exception{
while(keyvals != null) {
ISeq r = keyvals.next();
if(r == null)
throw new Exception("Malformed keyword argslist");
if(keyvals.first() == key)
return r;
keyvals = r.next();
}
return null;
}
}
else if(coll instanceof Map.Entry) {
Map.Entry e = (Map.Entry) coll;
if(n == 0)
return e.getKey();
else if(n == 1)
return e.getValue();
return notFound;
}
else if(coll instanceof Sequential) {
ISeq seq = RT.seq(coll);
coll = null;
for(int i = 0;
i <= n && seq != null;
++i, seq = seq.next()) {
if(i == n)
return seq.first();
}
return notFound;
}
else
throw new UnsupportedOperationException(
"nth not supported on this type: " +
coll.getClass().getSimpleName());
}
/**
* ********************* Boxing/casts ******************************
*/
static public Object box(Object x){
return x;
1114 CHAPTER 9. JVM/CLOJURE/LANG/
return (char) n;
}
9.95. RT.JAVA 1115
return (byte) n;
}
return (short) n;
}
return (float) n;
return (float) x;
}
/**
* **** list support ****
*/
((PushbackReader) r).unread(ret);
}
else {
r.mark(1);
ret = r.read();
r.reset();
}
return readRet(ret);
}
break;
case \t:
w.write("\\t");
break;
case \r:
w.write("\\r");
break;
case ":
w.write("\\\"");
break;
case \\:
w.write("\\\\");
break;
case \f:
w.write("\\f");
break;
case \b:
w.write("\\b");
break;
default:
w.write(c);
}
}
w.write(");
}
}
else if(x instanceof IPersistentMap) {
w.write({);
for(ISeq s = seq(x); s != null; s = s.next()) {
IMapEntry e = (IMapEntry) s.first();
print(e.key(), w);
w.write( );
print(e.val(), w);
if(s.next() != null)
w.write(", ");
}
w.write(});
}
else if(x instanceof IPersistentVector) {
IPersistentVector a = (IPersistentVector) x;
w.write([);
for(int i = 0; i < a.count(); i++) {
print(a.nth(i), w);
if(i < a.count() - 1)
w.write( );
}
w.write(]);
}
else if(x instanceof IPersistentSet) {
w.write("#{");
1130 CHAPTER 9. JVM/CLOJURE/LANG/
w.write(x.toString());
w.write("BIGINT");
}
else if(x instanceof Var) {
Var v = (Var) x;
w.write("#=(var " + v.ns.name + "/" + v.sym + ")");
}
else if(x instanceof Pattern) {
Pattern p = (Pattern) x;
w.write("#\"" + p.pattern() + "\"");
}
else w.write(x.toString());
}
//*/
}
w.write("tab");
break;
case :
w.write("space");
break;
case \b:
w.write("backspace");
break;
case \f:
w.write("formfeed");
break;
default:
w.write(c);
}
}
else
w.write(obj.toString());
}
return v;
}
xs[i] = v;
return v;
}
}
1138 CHAPTER 9. JVM/CLOJURE/LANG/
9.96 Script.java
Script.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Oct 18, 2007 */
package clojure.lang;
import clojure.main;
9.97 SeqEnumeration.java
(Enumeration [1723])
SeqEnumeration.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 3, 2008 */
package clojure.lang;
import java.util.Enumeration;
9.98 SeqIterator.java
(Iterator [1723])
SeqIterator.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jun 19, 2007 */
package clojure.lang;
import java.util.Iterator;
import java.util.NoSuchElementException;
ISeq seq;
9.99 Seqable.java
Seqable.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jan 28, 2009 */
package clojure.lang;
9.100 Sequential.java
Sequential.java
/*
\getchunk{Clojure Copyright}
*/
package clojure.lang;
9.101 Settable.java
Settable.java
9.102. SORTED.JAVA 1141
/*
\getchunk{Clojure Copyright}
*/
/* rich Dec 31, 2008 */
package clojure.lang;
9.102 Sorted.java
Sorted.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Apr 15, 2008 */
package clojure.lang;
import java.util.Comparator;
9.103 StringSeq.java
(ASeq [571]) (IndexedSeq [799])
StringSeq.java
/*
1142 CHAPTER 9. JVM/CLOJURE/LANG/
\getchunk{Clojure Copyright}
*/
/* rich Dec 6, 2007 */
package clojure.lang;
-
9.104. SYMBOL.JAVA 1143
9.104 Symbol.java
(AFn [509]) (IObj [800]) (Comparable [1723]) (Named [861]) (Serializable [1723])
Symbol.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Mar 25, 2006 11:42:47 AM */
package clojure.lang;
import java.io.Serializable;
import java.io.ObjectStreamException;
return _meta;
}
}
9.105 TransactionalHashMap.java
(AbstractMap [1723]) (ConcurrentMap [1723])
TransactionalHashMap.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 31, 2008 */
package clojure.lang;
import java.util.concurrent.ConcurrentMap;
import java.util.*;
r.set(map.without(k));
}
catch(Exception e)
{
throw new RuntimeException(e);
}
return (V) ret;
}
Ref r = bins[binFor(k)];
IPersistentMap map = (IPersistentMap) r.deref();
Entry e = map.entryAt(k);
if(e == null)
{
r.set(map.assoc(k, v));
return null;
}
else
return (V) e.getValue();
}
{
r.set(map.assoc(k, v));
return (V) e.getValue();
}
return null;
}
9.106 Util.java
Util.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Apr 19, 2008 */
package clojure.lang;
import java.math.BigInteger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.ref.SoftReference;
import java.lang.ref.ReferenceQueue;
}
}
}
}
9.107 Var.java
(ARef [553]) (IFn [774]) (IRef [805]) (Settable [1140])
Var.java
/*
\getchunk{Clojure Copyright}
*/
/* rich Jul 31, 2007 */
package clojure.lang;
import java.util.concurrent.atomic.AtomicBoolean;
public final class Var extends ARef implements IFn, IRef, Settable{
public Frame(){
this(PersistentHashMap.EMPTY, null);
}
//IPersistentMap _meta;
Frame f = dvals.get();
if(f != null)
return f;
return new Frame();
}
9.108 XMLHandler.java
(DefaultHandler [1723])
XMLHandler.java
9.108. XMLHANDLER.JAVA 1165
/*
\getchunk{Clojure Copyright}
*/
/* rich Dec 17, 2007 */
package clojure.lang;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/*
public static void main(String[] args){
try
{
ContentHandler dummy = new DefaultHandler();
SAXParserFactory f = SAXParserFactory.newInstance();
//f.setNamespaceAware(true);
SAXParser p = f.newSAXParser();
p.parse("http://arstechnica.com/journals.rssx",
new XMLHandler(dummy));
}
catch(Exception e)
{
e.printStackTrace();
}
}
//*/
}
-
Chapter 10
jvm/clojure
10.1 main.java
main.java
/*
\getchunk{Clojure Copyright}
*/
package clojure;
import clojure.lang.Symbol;
import clojure.lang.Var;
import clojure.lang.RT;
1167
1168 CHAPTER 10. JVM/CLOJURE
-
Chapter 11
clj/clojure/
11.1 core.clj
core.clj
\getchunk{Clojure Copyright}
(def unquote)
(def unquote-splicing)
(def
^{:arglists ([& items])
:doc "Creates a new list containing the items."
:added "1.0"}
list (. clojure.lang.PersistentList creator))
(def
^{:arglists ([x seq])
:doc "Returns a new seq where x is the first element and seq is
the rest."
:added "1.0"
:static true}
1169
1170 CHAPTER 11. CLJ/CLOJURE/
^{:macro true
:added "1.0"}
let (fn* let [&form &env & decl] (cons let* decl)))
(def
^{:macro true
:added "1.0"}
loop (fn* loop [&form &env & decl] (cons loop* decl)))
(def
^{:macro true
:added "1.0"}
fn (fn* fn [&form &env & decl]
(.withMeta ^clojure.lang.IObj (cons fn* decl)
(.meta ^clojure.lang.IMeta &form))))
(def
^{:arglists ([coll])
:doc "Returns the first item in the collection. Calls seq on its
argument. If coll is nil, returns nil."
:added "1.0"
:static true}
first (fn ^:static first [coll] (. clojure.lang.RT (first coll))))
(def
^{:arglists ([coll])
:tag clojure.lang.ISeq
:doc "Returns a seq of the items after the first. Calls seq on its
argument. If there are no more items, returns nil."
:added "1.0"
:static true}
next (fn ^:static next [x] (. clojure.lang.RT (next x))))
(def
^{:arglists ([coll])
:tag clojure.lang.ISeq
:doc "Returns a possibly empty seq of the items after the first.
Calls seq on its argument."
:added "1.0"
:static true}
rest (fn ^:static rest [x] (. clojure.lang.RT (more x))))
(def
^{:arglists ([coll x] [coll x & xs])
:doc "conj[oin]. Returns a new collection with the xs
added. (conj nil item) returns (item). The addition may
happen at different places depending on the concrete type."
:added "1.0"
:static true}
conj (fn ^:static conj
11.1. CORE.CLJ 1171
(def
^{:doc "Same as (first (next x))"
:arglists ([x])
:added "1.0"
:static true}
second (fn ^:static second [x] (first (next x))))
(def
^{:doc "Same as (first (first x))"
:arglists ([x])
:added "1.0"
:static true}
ffirst (fn ^:static ffirst [x] (first (first x))))
(def
^{:doc "Same as (next (first x))"
:arglists ([x])
:added "1.0"
:static true}
nfirst (fn ^:static nfirst [x] (next (first x))))
(def
^{:doc "Same as (first (next x))"
:arglists ([x])
:added "1.0"
:static true}
fnext (fn ^:static fnext [x] (first (next x))))
(def
^{:doc "Same as (next (next x))"
:arglists ([x])
:added "1.0"
:static true}
nnext (fn ^:static nnext [x] (next (next x))))
(def
^{:arglists (^clojure.lang.ISeq [coll])
:doc "Returns a seq on the collection. If the collection is
empty, returns nil. (seq nil) returns nil. seq also works on
Strings, native Java arrays (of reference types) and any objects
that implement Iterable."
:tag clojure.lang.ISeq
:added "1.0"
:static true}
1172 CHAPTER 11. CLJ/CLOJURE/
(def
^{:arglists ([^Class c x])
:doc "Evaluates x and tests if it is an instance of the class
c. Returns true or false"
:added "1.0"}
instance? (fn instance? [^Class c x] (. c (isInstance x))))
(def
^{:arglists ([x])
:doc "Return true if x implements ISeq"
:added "1.0"
:static true}
seq? (fn ^:static seq? [x] (instance? clojure.lang.ISeq x)))
(def
^{:arglists ([x])
:doc "Return true if x is a Character"
:added "1.0"
:static true}
char? (fn ^:static char? [x] (instance? Character x)))
(def
^{:arglists ([x])
:doc "Return true if x is a String"
:added "1.0"
:static true}
string? (fn ^:static string? [x] (instance? String x)))
(def
^{:arglists ([x])
:doc "Return true if x implements IPersistentMap"
:added "1.0"
:static true}
map? (fn ^:static map? [x] (instance? clojure.lang.IPersistentMap x)))
(def
^{:arglists ([x])
:doc "Return true if x implements IPersistentVector"
:added "1.0"
:static true}
vector? (fn ^:static vector? [x]
(instance? clojure.lang.IPersistentVector x)))
(def
^{:arglists ([map key val] [map key val & kvs])
:doc "assoc[iate]. When applied to a map, returns a new map of the
same (hashed/sorted) type, that contains the mapping of key(s) to
11.1. CORE.CLJ 1173
(def
^{:arglists ([^clojure.lang.IObj obj m])
:doc "Returns an object of the same type and value as obj, with
map m as its metadata."
:added "1.0"
:static true}
with-meta (fn ^:static with-meta [^clojure.lang.IObj x m]
(. x (withMeta m))))
(def
^{:private true}
sigs
(fn [fdecl]
(assert-valid-fdecl fdecl)
(let [asig
(fn [fdecl]
(let [arglist (first fdecl)
;elide implicit macro args
arglist
(if
(clojure.lang.Util/equals &form (first arglist))
(clojure.lang.RT/subvec arglist 2
(clojure.lang.RT/count arglist))
1174 CHAPTER 11. CLJ/CLOJURE/
arglist)
body (next fdecl)]
(if (map? (first body))
(if (next body)
(with-meta arglist
(conj
(if (meta arglist) (meta arglist) {})
(first body)))
arglist)
arglist)))]
(if (seq? (first fdecl))
(loop [ret [] fdecls fdecl]
(if fdecls
(recur (conj ret (asig (first fdecls))) (next fdecls))
(seq ret)))
(list (asig fdecl))))))
(def
^{:arglists ([coll])
:doc "Return the last item in coll, in linear time"
:added "1.0"
:static true}
last (fn ^:static last [s]
(if (next s)
(recur (next s))
(first s))))
(def
^{:arglists ([coll])
:doc "Return a seq of all but the last item in coll, in linear time"
:added "1.0"
:static true}
butlast (fn ^:static butlast [s]
(loop [ret [] s s]
(if (next s)
(recur (conj ret (first s)) (next s))
(seq ret)))))
(def
(defn cast
1176 CHAPTER 11. CLJ/CLOJURE/
(defn to-array
"Returns an array of Objects containing the contents of coll, which
can be any Collection. Maps to java.util.Collection.toArray()."
{:tag "[Ljava.lang.Object;"
:added "1.0"
:static true}
[coll] (. clojure.lang.RT (toArray coll)))
(defn vector
"Creates a new vector containing the args."
{:added "1.0"
:static true}
([] [])
([a] [a])
([a b] [a b])
([a b c] [a b c])
([a b c d] [a b c d])
([a b c d & args]
(. clojure.lang.LazilyPersistentVector
(create (cons a (cons b (cons c (cons d args))))))))
(defn vec
"Creates a new vector containing the contents of coll."
{:added "1.0"
:static true}
([coll]
(if (instance? java.util.Collection coll)
(clojure.lang.LazilyPersistentVector/create coll)
(. clojure.lang.LazilyPersistentVector
(createOwning (to-array coll))))))
(defn hash-map
"keyval => key val
Returns a new hash map with supplied mappings."
{:added "1.0"
:static true}
([] {})
([& keyvals]
(. clojure.lang.PersistentHashMap (createWithCheck keyvals))))
(defn hash-set
"Returns a new hash set with supplied keys."
{:added "1.0"
:static true}
11.1. CORE.CLJ 1177
([] #{})
([& keys]
(clojure.lang.PersistentHashSet/createWithCheck keys)))
(defn sorted-map
"keyval => key val
Returns a new sorted map with supplied mappings."
{:added "1.0"
:static true}
([& keyvals]
(clojure.lang.PersistentTreeMap/create keyvals)))
(defn sorted-map-by
"keyval => key val
Returns a new sorted map with supplied mappings,
using the supplied comparator."
{:added "1.0"
:static true}
([comparator & keyvals]
(clojure.lang.PersistentTreeMap/create comparator keyvals)))
(defn sorted-set
"Returns a new sorted set with supplied keys."
{:added "1.0"
:static true}
([& keys]
(clojure.lang.PersistentTreeSet/create keys)))
(defn sorted-set-by
"Returns a new sorted set with supplied keys,
using the supplied comparator."
{:added "1.1"
:static true}
([comparator & keys]
(clojure.lang.PersistentTreeSet/create comparator keys)))
;;;;;;;;;;;;;;;;;;;;
(defn nil?
"Returns true if x is nil, false otherwise."
{:tag Boolean
:added "1.0"
:static true
:inline (fn [x] (list clojure.lang.Util/identical x nil))}
[x] (clojure.lang.Util/identical x nil))
(def
called."
:arglists
([name doc-string? attr-map? [params*] body]
[name doc-string? attr-map? ([params*] body)+ attr-map?])
:added "1.0"}
defmacro (fn [&form &env
name & args]
(let [prefix (loop [p (list name) args args]
(let [f (first args)]
(if (string? f)
(recur (cons f p) (next args))
(if (map? f)
(recur (cons f p) (next args))
p))))
fdecl (loop [fd args]
(if (string? (first fd))
(recur (next fd))
(if (map? (first fd))
(recur (next fd))
fd)))
fdecl (if (vector? (first fdecl))
(list fdecl)
fdecl)
add-implicit-args (fn [fd]
(let [args (first fd)]
(cons
(vec (cons &form (cons &env args)))
(next fd))))
add-args (fn [acc ds]
(if (nil? ds)
acc
(let [d (first ds)]
(if (map? d)
(conj acc d)
(recur
(conj acc (add-implicit-args d))
(next ds))))))
fdecl (seq (add-args [] fdecl))
decl (loop [p prefix d fdecl]
(if p
(recur (next p) (cons (first p) d))
d))]
(list do
(cons defn decl)
(list . (list var name) (setMacro))
(list var name)))))
(defmacro when
"Evaluates test. If logical true, evaluates body in an implicit do."
{:added "1.0"}
[test & body]
(list if test (cons do body)))
(defmacro when-not
"Evaluates test. If logical false, evaluates body in an implicit do."
{:added "1.0"}
[test & body]
(list if test nil (cons do body)))
(defn false?
"Returns true if x is the value false, false otherwise."
{:tag Boolean,
:added "1.0"
:static true}
[x] (clojure.lang.Util/identical x false))
(defn true?
"Returns true if x is the value true, false otherwise."
{:tag Boolean,
:added "1.0"
:static true}
[x] (clojure.lang.Util/identical x true))
(defn not
"Returns true if x is logical false, false otherwise."
{:tag Boolean
:added "1.0"
:static true}
[x] (if x false true))
(defn str
"With no args, returns the empty string. With one arg x, returns
x.toString(). (str nil) returns the empty string. With more than
one arg, returns the concatenation of the str values of the args."
{:tag String
:added "1.0"
:static true}
(^String [] "")
(^String [^Object x]
(if (nil? x) "" (. x (toString))))
(^String [x & ys]
((fn [^StringBuilder sb more]
(if more
(recur (. sb (append (str (first more)))) (next more))
(str sb)))
(new StringBuilder (str x)) ys)))
1180 CHAPTER 11. CLJ/CLOJURE/
(defn symbol?
"Return true if x is a Symbol"
{:added "1.0"
:static true}
[x] (instance? clojure.lang.Symbol x))
(defn keyword?
"Return true if x is a Keyword"
{:added "1.0"
:static true}
[x] (instance? clojure.lang.Keyword x))
(defn symbol
"Returns a Symbol with the given namespace and name."
{:tag clojure.lang.Symbol
:added "1.0"
:static true}
([name] (if (symbol? name) name (clojure.lang.Symbol/intern name)))
([ns name] (clojure.lang.Symbol/intern ns name)))
(defn gensym
"Returns a new symbol with a unique name. If a prefix string is
supplied, the name is prefix# where # is some unique number. If
prefix is not supplied, the prefix is G__."
{:added "1.0"
:static true}
([] (gensym "G__"))
([prefix-string]
(. clojure.lang.Symbol
(intern
(str prefix-string (str (. clojure.lang.RT (nextID))))))))
(defmacro cond
"Takes a set of test/expr pairs. It evaluates each test one at a
time. If a test returns logical true, cond evaluates and returns
the value of the corresponding expr and doesnt evaluate any of the
other tests or exprs. (cond) returns nil."
{:added "1.0"}
[& clauses]
(when clauses
(list if (first clauses)
(if (next clauses)
(second clauses)
(throw (IllegalArgumentException.
"cond requires an even number of forms")))
(cons clojure.core/cond (next (next clauses))))))
(defn keyword
"Returns a Keyword with the given namespace and name. Do not use :
11.1. CORE.CLJ 1181
(defn find-keyword
"Returns a Keyword with the given namespace and name if one already
exists. This function will not intern a new keyword. If the keyword
has not already been interned, it will return nil. Do not use :
in the keyword strings, it will be added automatically."
{:tag clojure.lang.Keyword
:added "1.3"
:static true}
([name] (cond (keyword? name) name
(symbol? name)
(clojure.lang.Keyword/find ^clojure.lang.Symbol name)
(string? name)
(clojure.lang.Keyword/find ^String name)))
([ns name] (clojure.lang.Keyword/find ns name)))
(defn spread
{:private true
:static true}
[arglist]
(cond
(nil? arglist) nil
(nil? (next arglist)) (seq (first arglist))
:else (cons (first arglist) (spread (next arglist)))))
(defn list*
"Creates a new list containing the items prepended to the rest, the
last of which will be treated as a sequence."
{:added "1.0"
:static true}
([args] (seq args))
([a args] (cons a args))
([a b args] (cons a (cons b args)))
([a b c args] (cons a (cons b (cons c args))))
([a b c d & more]
(cons a (cons b (cons c (cons d (spread more)))))))
(defn apply
"Applies fn f to the argument list formed by prepending
1182 CHAPTER 11. CLJ/CLOJURE/
(defn vary-meta
"Returns an object of the same type and value as obj, with
(apply f (meta obj) args) as its metadata."
{:added "1.0"
:static true}
[obj f & args]
(with-meta obj (apply f (meta obj) args)))
(defmacro lazy-seq
"Takes a body of expressions that returns an ISeq or nil, and yields
a Seqable object that will invoke the body only the first time seq
is called, and will cache the result and return it on all subsequent
seq calls."
{:added "1.0"}
[& body]
(list new clojure.lang.LazySeq (list* ^{:once true} fn* [] body)))
^clojure.lang.ISeq [^clojure.lang.IChunkedSeq s]
(.chunkedNext s))
(defn concat
"Returns a lazy seq representing the concatenation of the
elements in the supplied colls."
{:added "1.0"
:static true}
([] (lazy-seq nil))
([x] (lazy-seq x))
([x y]
(lazy-seq
(let [s (seq x)]
(if s
(if (chunked-seq? s)
(chunk-cons (chunk-first s) (concat (chunk-rest s) y))
(cons (first s) (concat (rest s) y)))
y))))
([x y & zs]
(let [cat (fn cat [xys zs]
(lazy-seq
(let [xys (seq xys)]
(if xys
(if (chunked-seq? xys)
(chunk-cons (chunk-first xys)
(cat (chunk-rest xys) zs))
(cons (first xys) (cat (rest xys) zs)))
(when zs
(cat (first zs) (next zs)))))))]
(cat (concat x y) zs))))
(defn delay?
1184 CHAPTER 11. CLJ/CLOJURE/
(defn force
"If x is a Delay, returns the (possibly cached) value of its
expression, else returns x"
{:added "1.0"
:static true}
[x] (. clojure.lang.Delay (force x)))
(defmacro if-not
"Evaluates test. If logical false, evaluates and returns then expr,
otherwise else expr, if supplied, else nil."
{:added "1.0"}
([test then] (if-not ~test ~then nil))
([test then else]
(if (not ~test) ~then ~else)))
(defn identical?
"Tests if 2 arguments are the same object"
{:inline (fn [x y] (. clojure.lang.Util identical ~x ~y))
:inline-arities #{2}
:added "1.0"}
([x y] (clojure.lang.Util/identical x y)))
;equiv-based
(defn =
"Equality. Returns true if x equals y, false if not. Same as
Java x.equals(y) except it also works for nil, and compares
numbers and collections in a type-independent manner. Clojures
immutable data structures define equals() (and thus =) as a value,
not an identity, comparison."
{:inline (fn [x y] (. clojure.lang.Util equiv ~x ~y))
:inline-arities #{2}
:added "1.0"}
([x] true)
([x y] (clojure.lang.Util/equiv x y))
([x y & more]
(if (= x y)
(if (next more)
(recur y (first more) (next more))
(= y (first more)))
false)))
;equals-based
#_(defn =
"Equality. Returns true if x equals y, false if not. Same as Java
x.equals(y) except it also works for nil. Boxed numbers must have
11.1. CORE.CLJ 1185
(defn not=
"Same as (not (= obj1 obj2))"
{:tag Boolean
:added "1.0"
:static true}
([x] false)
([x y] (not (= x y)))
([x y & more]
(not (apply = x y more))))
(defn compare
"Comparator. Returns a negative number, zero, or a positive number
when x is logically less than, equal to, or greater than
y. Same as Java x.compareTo(y) except it also works for nil, and
compares numbers and collections in a type-independent manner. x
must implement Comparable"
{
:inline (fn [x y] (. clojure.lang.Util compare ~x ~y))
:added "1.0"}
[x y] (. clojure.lang.Util (compare x y)))
(defmacro and
"Evaluates exprs one at a time, from left to right. If a form
returns logical false (nil or false), and returns that value and
doesnt evaluate any of the other expressions, otherwise it returns
the value of the last expr. (and) returns true."
{:added "1.0"}
([] true)
([x] x)
([x & next]
(let [and# ~x]
(if and# (and ~@next) and#))))
(defmacro or
1186 CHAPTER 11. CLJ/CLOJURE/
(defn count
"Returns the number of items in the collection. (count nil) returns
0. Also works on strings, arrays, and Java Collections and Maps"
{
:inline (fn [x] (. clojure.lang.RT (count ~x)))
:added "1.0"}
[coll] (clojure.lang.RT/count coll))
(defn int
"Coerce to int"
{
:inline (fn [x]
(. clojure.lang.RT
(~(if *unchecked-math* uncheckedIntCast intCast) ~x)))
:added "1.0"}
[x] (. clojure.lang.RT (intCast x)))
(defn nth
"Returns the value at the index. get returns nil if index out of
bounds, nth throws an exception unless not-found is supplied. nth
also works for strings, Java arrays, regex Matchers and Lists, and,
in O(n) time, for sequences."
{:inline (fn [c i & nf] (. clojure.lang.RT (nth ~c ~i ~@nf)))
:inline-arities #{2 3}
:added "1.0"}
([coll index] (. clojure.lang.RT (nth coll index)))
([coll index not-found]
(. clojure.lang.RT (nth coll index not-found))))
(defn <
"Returns non-nil if nums are in monotonically increasing order,
11.1. CORE.CLJ 1187
otherwise false."
{:inline (fn [x y] (. clojure.lang.Numbers (lt ~x ~y)))
:inline-arities #{2}
:added "1.0"}
([x] true)
([x y] (. clojure.lang.Numbers (lt x y)))
([x y & more]
(if (< x y)
(if (next more)
(recur y (first more) (next more))
(< y (first more)))
false)))
(defn inc
"Returns a number one greater than num. Supports arbitrary precision.
See also: inc"
{:inline (fn [x] (. clojure.lang.Numbers (incP ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (incP x)))
(defn inc
"Returns a number one greater than num. Does not auto-promote
longs, will throw on overflow. See also: inc"
{:inline (fn [x]
(. clojure.lang.Numbers
(~(if *unchecked-math* unchecked_inc inc) ~x)))
:added "1.2"}
[x] (. clojure.lang.Numbers (inc x)))
(defn reverse
"Returns a seq of the items in coll in reverse order. Not lazy."
{:added "1.0"
1188 CHAPTER 11. CLJ/CLOJURE/
:static true}
[coll]
(reduce1 conj () coll))
;;math stuff
(defn +
"Returns the sum of nums. (+) returns 0. Supports arbitrary precision.
See also: +"
{:inline (fn [x y] (. clojure.lang.Numbers (addP ~x ~y)))
:inline-arities #{2}
:added "1.0"}
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (addP x y)))
([x y & more]
(reduce1 + (+ x y) more)))
(defn +
"Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +"
{:inline (fn [x y]
(. clojure.lang.Numbers
(~(if *unchecked-math* unchecked_add add) ~x ~y)))
:inline-arities #{2}
:added "1.2"}
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (add x y)))
([x y & more]
(reduce1 + (+ x y) more)))
(defn *
"Returns the product of nums. (*) returns 1. Supports arbitrary
precision. See also: *"
{:inline (fn [x y] (. clojure.lang.Numbers (multiplyP ~x ~y)))
:inline-arities #{2}
:added "1.0"}
([] 1)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (multiplyP x y)))
([x y & more]
(reduce1 * (* x y) more)))
(defn *
"Returns the product of nums. (*) returns 1. Does not auto-promote
longs, will throw on overflow. See also: *"
{:inline
(fn [x y]
(. clojure.lang.Numbers
(~(if *unchecked-math* unchecked_multiply multiply) ~x ~y)))
11.1. CORE.CLJ 1189
:inline-arities #{2}
:added "1.2"}
([] 1)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (multiply x y)))
([x y & more]
(reduce1 * (* x y) more)))
(defn /
"If no denominators are supplied, returns 1/numerator,
else returns numerator divided by all of the denominators."
{:inline (fn [x y] (. clojure.lang.Numbers (divide ~x ~y)))
:inline-arities #{2}
:added "1.0"}
([x] (/ 1 x))
([x y] (. clojure.lang.Numbers (divide x y)))
([x y & more]
(reduce1 / (/ x y) more)))
(defn -
"If no ys are supplied, returns the negation of x, else subtracts
the ys from x and returns the result. Supports arbitrary precision.
See also: -"
{:inline (fn [& args] (. clojure.lang.Numbers (minusP ~@args)))
:inline-arities #{1 2}
:added "1.0"}
([x] (. clojure.lang.Numbers (minusP x)))
([x y] (. clojure.lang.Numbers (minusP x y)))
([x y & more]
(reduce1 - (- x y) more)))
(defn -
"If no ys are supplied, returns the negation of x, else subtracts
the ys from x and returns the result. Does not auto-promote
longs, will throw on overflow. See also: -"
{:inline
(fn [& args]
(. clojure.lang.Numbers
(~(if *unchecked-math* unchecked_minus minus) ~@args)))
:inline-arities #{1 2}
:added "1.2"}
([x] (. clojure.lang.Numbers (minus x)))
([x y] (. clojure.lang.Numbers (minus x y)))
([x y & more]
(reduce1 - (- x y) more)))
(defn <=
"Returns non-nil if nums are in monotonically non-decreasing order,
otherwise false."
{:inline (fn [x y] (. clojure.lang.Numbers (lte ~x ~y)))
1190 CHAPTER 11. CLJ/CLOJURE/
:inline-arities #{2}
:added "1.0"}
([x] true)
([x y] (. clojure.lang.Numbers (lte x y)))
([x y & more]
(if (<= x y)
(if (next more)
(recur y (first more) (next more))
(<= y (first more)))
false)))
(defn >
"Returns non-nil if nums are in monotonically decreasing order,
otherwise false."
{:inline (fn [x y] (. clojure.lang.Numbers (gt ~x ~y)))
:inline-arities #{2}
:added "1.0"}
([x] true)
([x y] (. clojure.lang.Numbers (gt x y)))
([x y & more]
(if (> x y)
(if (next more)
(recur y (first more) (next more))
(> y (first more)))
false)))
(defn >=
"Returns non-nil if nums are in monotonically non-increasing order,
otherwise false."
{:inline (fn [x y] (. clojure.lang.Numbers (gte ~x ~y)))
:inline-arities #{2}
:added "1.0"}
([x] true)
([x y] (. clojure.lang.Numbers (gte x y)))
([x y & more]
(if (>= x y)
(if (next more)
(recur y (first more) (next more))
(>= y (first more)))
false)))
(defn ==
"Returns non-nil if nums all have the equivalent
value (type-independent), otherwise false"
{:inline (fn [x y] (. clojure.lang.Numbers (equiv ~x ~y)))
:inline-arities #{2}
:added "1.0"}
([x] true)
([x y] (. clojure.lang.Numbers (equiv x y)))
([x y & more]
11.1. CORE.CLJ 1191
(if (== x y)
(if (next more)
(recur y (first more) (next more))
(== y (first more)))
false)))
(defn max
"Returns the greatest of the nums."
{:added "1.0"
:static true}
([x] x)
([x y] (if (> x y) x y))
([x y & more]
(reduce1 max (max x y) more)))
(defn min
"Returns the least of the nums."
{:added "1.0"
:static true}
([x] x)
([x y] (if (< x y) x y))
([x y & more]
(reduce1 min (min x y) more)))
(defn dec
"Returns a number one less than num. Supports arbitrary precision.
See also: dec"
{:inline (fn [x] (. clojure.lang.Numbers (decP ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (decP x)))
(defn dec
"Returns a number one less than num. Does not auto-promote
longs, will throw on overflow. See also: dec"
{:inline
(fn [x]
(. clojure.lang.Numbers
(~(if *unchecked-math* unchecked_dec dec) ~x)))
:added "1.2"}
[x] (. clojure.lang.Numbers (dec x)))
(defn unchecked-inc-int
"Returns a number one greater than x, an int.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x] (. clojure.lang.Numbers (unchecked_int_inc ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (unchecked_int_inc x)))
(defn unchecked-inc
"Returns a number one greater than x, a long.
1192 CHAPTER 11. CLJ/CLOJURE/
(defn unchecked-dec-int
"Returns a number one less than x, an int.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x] (. clojure.lang.Numbers (unchecked_int_dec ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (unchecked_int_dec x)))
(defn unchecked-dec
"Returns a number one less than x, a long.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x] (. clojure.lang.Numbers (unchecked_dec ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (unchecked_dec x)))
(defn unchecked-negate-int
"Returns the negation of x, an int.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x] (. clojure.lang.Numbers (unchecked_int_negate ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (unchecked_int_negate x)))
(defn unchecked-negate
"Returns the negation of x, a long.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x] (. clojure.lang.Numbers (unchecked_minus ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (unchecked_minus x)))
(defn unchecked-add-int
"Returns the sum of x and y, both int.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x y] (. clojure.lang.Numbers (unchecked_int_add ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_int_add x y)))
(defn unchecked-add
"Returns the sum of x and y, both long.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x y] (. clojure.lang.Numbers (unchecked_add ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_add x y)))
(defn unchecked-subtract-int
"Returns the difference of x and y, both int.
Note - uses a primitive operator subject to overflow."
11.1. CORE.CLJ 1193
{:inline
(fn [x y] (. clojure.lang.Numbers (unchecked_int_subtract ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_int_subtract x y)))
(defn unchecked-subtract
"Returns the difference of x and y, both long.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x y] (. clojure.lang.Numbers (unchecked_minus ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_minus x y)))
(defn unchecked-multiply-int
"Returns the product of x and y, both int.
Note - uses a primitive operator subject to overflow."
{:inline
(fn [x y] (. clojure.lang.Numbers (unchecked_int_multiply ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_int_multiply x y)))
(defn unchecked-multiply
"Returns the product of x and y, both long.
Note - uses a primitive operator subject to overflow."
{:inline
(fn [x y] (. clojure.lang.Numbers (unchecked_multiply ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_multiply x y)))
(defn unchecked-divide-int
"Returns the division of x by y, both int.
Note - uses a primitive operator subject to truncation."
{:inline
(fn [x y] (. clojure.lang.Numbers (unchecked_int_divide ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_int_divide x y)))
(defn unchecked-remainder-int
"Returns the remainder of division of x by y, both int.
Note - uses a primitive operator subject to truncation."
{:inline
(fn [x y] (. clojure.lang.Numbers (unchecked_int_remainder ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers (unchecked_int_remainder x y)))
(defn pos?
"Returns true if num is greater than zero, else false"
{
:inline (fn [x] (. clojure.lang.Numbers (isPos ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (isPos x)))
1194 CHAPTER 11. CLJ/CLOJURE/
(defn neg?
"Returns true if num is less than zero, else false"
{
:inline (fn [x] (. clojure.lang.Numbers (isNeg ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (isNeg x)))
(defn quot
"quot[ient] of dividing numerator by denominator."
{:added "1.0"
:static true
:inline (fn [x y] (. clojure.lang.Numbers (quotient ~x ~y)))}
[num div]
(. clojure.lang.Numbers (quotient num div)))
(defn rem
"remainder of dividing numerator by denominator."
{:added "1.0"
:static true
:inline (fn [x y] (. clojure.lang.Numbers (remainder ~x ~y)))}
[num div]
(. clojure.lang.Numbers (remainder num div)))
(defn rationalize
"returns the rational value of num"
{:added "1.0"
:static true}
[num]
(. clojure.lang.Numbers (rationalize num)))
;;Bit ops
(defn bit-not
"Bitwise complement"
{:inline (fn [x] (. clojure.lang.Numbers (not ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers not x))
(defn bit-and
"Bitwise and"
{:inline (fn [x y] (. clojure.lang.Numbers (and ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers and x y))
(defn bit-or
"Bitwise or"
{:inline (fn [x y] (. clojure.lang.Numbers (or ~x ~y)))
:added "1.0"}
11.1. CORE.CLJ 1195
[x y] (. clojure.lang.Numbers or x y))
(defn bit-xor
"Bitwise exclusive or"
{:inline (fn [x y] (. clojure.lang.Numbers (xor ~x ~y)))
:added "1.0"}
[x y] (. clojure.lang.Numbers xor x y))
(defn bit-and-not
"Bitwise and with complement"
{:added "1.0"
:static true}
[x y] (. clojure.lang.Numbers andNot x y))
(defn bit-clear
"Clear bit at index n"
{:added "1.0"
:static true}
[x n] (. clojure.lang.Numbers clearBit x n))
(defn bit-set
"Set bit at index n"
{:added "1.0"
:static true}
[x n] (. clojure.lang.Numbers setBit x n))
(defn bit-flip
"Flip bit at index n"
{:added "1.0"
:static true}
[x n] (. clojure.lang.Numbers flipBit x n))
(defn bit-test
"Test bit at index n"
{:added "1.0"
:static true}
[x n] (. clojure.lang.Numbers testBit x n))
(defn bit-shift-left
"Bitwise shift left"
{:inline (fn [x n] (. clojure.lang.Numbers (shiftLeft ~x ~n)))
:added "1.0"}
[x n] (. clojure.lang.Numbers shiftLeft x n))
(defn bit-shift-right
"Bitwise shift right"
{:inline (fn [x n] (. clojure.lang.Numbers (shiftRight ~x ~n)))
:added "1.0"}
1196 CHAPTER 11. CLJ/CLOJURE/
(defn even?
"Returns true if n is even, throws an exception if n is not an integer"
{:added "1.0"
:static true}
[n] (zero? (bit-and n 1)))
(defn odd?
"Returns true if n is odd, throws an exception if n is not an integer"
{:added "1.0"
:static true}
[n] (not (even? n)))
;;
(defn complement
"Takes a fn f and returns a fn that takes the same arguments as f,
has the same effects, if any, and returns the opposite truth value."
{:added "1.0"
:static true}
[f]
(fn
([] (not (f)))
([x] (not (f x)))
([x y] (not (f x y)))
([x y & zs] (not (apply f x y zs)))))
(defn constantly
"Returns a function that takes any number of arguments and returns x."
{:added "1.0"
:static true}
[x] (fn [& args] x))
(defn identity
"Returns its argument."
{:added "1.0"
:static true}
[x] x)
;;Collection stuff
;;list stuff
(defn peek
"For a list or queue, same as first, for a vector, same as, but much
more efficient than, last. If the collection is empty, returns nil."
{:added "1.0"
:static true}
[coll] (. clojure.lang.RT (peek coll)))
11.1. CORE.CLJ 1197
(defn pop
"For a list or queue, returns a new list/queue without the first
item, for a vector, returns a new vector without the last item. If
the collection is empty, throws an exception. Note - not the same
as next/butlast."
{:added "1.0"
:static true}
[coll] (. clojure.lang.RT (pop coll)))
;;map stuff
(defn contains?
"Returns true if key is present in the given collection, otherwise
returns false. Note that for numerically indexed collections like
vectors and Java arrays, this tests if the numeric key is within the
range of indexes. contains? operates constant or logarithmic time;
it will not perform a linear search for a value. See also some."
{:added "1.0"
:static true}
[coll key] (. clojure.lang.RT (contains coll key)))
(defn get
"Returns the value mapped to key, not-found or nil if key not present."
{:inline (fn [m k & nf] (. clojure.lang.RT (get ~m ~k ~@nf)))
:inline-arities #{2 3}
:added "1.0"}
([map key]
(. clojure.lang.RT (get map key)))
([map key not-found]
(. clojure.lang.RT (get map key not-found))))
(defn dissoc
"dissoc[iate]. Returns a new map of the same (hashed/sorted) type,
that does not contain a mapping for key(s)."
{:added "1.0"
:static true}
([map] map)
([map key]
(. clojure.lang.RT (dissoc map key)))
([map key & ks]
(let [ret (dissoc map key)]
(if ks
(recur ret (first ks) (next ks))
ret))))
(defn disj
"disj[oin]. Returns a new set of the same (hashed/sorted) type, that
does not contain key(s)."
{:added "1.0"
1198 CHAPTER 11. CLJ/CLOJURE/
:static true}
([set] set)
([^clojure.lang.IPersistentSet set key]
(when set
(. set (disjoin key))))
([set key & ks]
(when set
(let [ret (disj set key)]
(if ks
(recur ret (first ks) (next ks))
ret)))))
(defn find
"Returns the map entry for key, or nil if key not present."
{:added "1.0"
:static true}
[map key] (. clojure.lang.RT (find map key)))
(defn select-keys
"Returns a map containing only those entries in map whose key
is in keys"
{:added "1.0"
:static true}
[map keyseq]
(loop [ret {} keys (seq keyseq)]
(if keys
(let [entry (. clojure.lang.RT (find map (first keys)))]
(recur
(if entry
(conj ret entry)
ret)
(next keys)))
ret)))
(defn keys
"Returns a sequence of the maps keys."
{:added "1.0"
:static true}
[map] (. clojure.lang.RT (keys map)))
(defn vals
"Returns a sequence of the maps values."
{:added "1.0"
:static true}
[map] (. clojure.lang.RT (vals map)))
(defn key
"Returns the key of the map entry."
{:added "1.0"
:static true}
11.1. CORE.CLJ 1199
[^java.util.Map$Entry e]
(. e (getKey)))
(defn val
"Returns the value in the map entry."
{:added "1.0"
:static true}
[^java.util.Map$Entry e]
(. e (getValue)))
(defn rseq
"Returns, in constant time, a seq of the items in rev (which
can be a vector or sorted-map), in reverse order. If rev is
empty returns nil"
{:added "1.0"
:static true}
[^clojure.lang.Reversible rev]
(. rev (rseq)))
(defn name
"Returns the name String of a string, symbol or keyword."
{:tag String
:added "1.0"
:static true}
[x]
(if (string? x) x (. ^clojure.lang.Named x (getName))))
(defn namespace
"Returns the namespace String of a symbol or keyword, or nil
if not present."
{:tag String
:added "1.0"
:static true}
[^clojure.lang.Named x]
(. x (getNamespace)))
(defmacro locking
"Executes exprs in an implicit do, while holding the monitor of x.
Will release the monitor of x in all circumstances."
{:added "1.0"}
[x & body]
(let [lockee# ~x]
(try
(monitor-enter lockee#)
~@body
(finally
(monitor-exit lockee#)))))
(defmacro ..
"form => fieldName-symbol or (instanceMethodName-symbol args*)
1200 CHAPTER 11. CLJ/CLOJURE/
Expands into a member access (.) of the first member on the first
argument, followed by the next member on the result, etc. For
instance:
expands to:
(defmacro ->
"Threads the expr through the forms. Inserts x as the
second item in the first form, making a list of it if it is not a
list already. If there are more forms, inserts the first form as the
second item in second form, etc."
{:added "1.0"}
([x] x)
([x form] (if (seq? form)
(with-meta (~(first form) ~x ~@(next form)) (meta form))
(list form x)))
([x form & more] (-> (-> ~x ~form) ~@more)))
(defmacro ->>
"Threads the expr through the forms. Inserts x as the
last item in the first form, making a list of it if it is not a
list already. If there are more forms, inserts the first form as the
last item in second form, etc."
{:added "1.1"}
([x form] (if (seq? form)
(with-meta (~(first form) ~@(next form) ~x) (meta form))
(list form x)))
([x form & more] (->> (->> ~x ~form) ~@more)))
;;multimethods
(def global-hierarchy)
(defmacro defmulti
"Creates a new multimethod with the associated dispatch function.
The docstring and attribute-map are optional.
(defmacro defmethod
"Creates and installs a new method of multimethod associated
with dispatch-value. "
{:added "1.0"}
[multifn dispatch-val & fn-tail]
(. ~(with-meta multifn {:tag clojure.lang.MultiFn})
addMethod ~dispatch-val (fn ~@fn-tail)))
(defn remove-all-methods
"Removes all of the methods of multimethod."
{:added "1.2"
:static true}
[^clojure.lang.MultiFn multifn]
1202 CHAPTER 11. CLJ/CLOJURE/
(.reset multifn))
(defn remove-method
"Removes the method of multimethod associated with dispatch-value."
{:added "1.0"
:static true}
[^clojure.lang.MultiFn multifn dispatch-val]
(. multifn removeMethod dispatch-val))
(defn prefer-method
"Causes the multimethod to prefer matches of dispatch-val-x
over dispatch-val-y
when there is a conflict"
{:added "1.0"
:static true}
[^clojure.lang.MultiFn multifn dispatch-val-x dispatch-val-y]
(. multifn preferMethod dispatch-val-x dispatch-val-y))
(defn methods
"Given a multimethod, returns a map of dispatch values -> dispatch fns"
{:added "1.0"
:static true}
[^clojure.lang.MultiFn multifn] (.getMethodTable multifn))
(defn get-method
"Given a multimethod and a dispatch value, returns the dispatch fn
that would apply to that value, or nil if none apply and no default"
{:added "1.0"
:static true}
[^clojure.lang.MultiFn multifn dispatch-val]
(.getMethod multifn dispatch-val))
(defn prefers
"Given a multimethod, returns a map of preferred value -> set
of other values"
{:added "1.0"
:static true}
[^clojure.lang.MultiFn multifn] (.getPreferTable multifn))
(defmacro if-let
11.1. CORE.CLJ 1203
(defmacro when-let
"bindings => binding-form test
(defn push-thread-bindings
"WARNING: This is a low-level function. Prefer high-level macros like
binding where ever possible.
(push-thread-bindings bindings)
(try
...
(finally
(pop-thread-bindings)))"
{:added "1.1"
:static true}
[bindings]
1204 CHAPTER 11. CLJ/CLOJURE/
(clojure.lang.Var/pushThreadBindings bindings))
(defn pop-thread-bindings
"Pop one set of bindings pushed with push-binding before. It is an
error to pop bindings without pushing before."
{:added "1.1"
:static true}
[]
(clojure.lang.Var/popThreadBindings))
(defn get-thread-bindings
"Get a map with the Var/value pairs which is currently in effect
for the current thread."
{:added "1.1"
:static true}
[]
(clojure.lang.Var/getThreadBindings))
(defmacro binding
"binding => var-symbol init-expr
(defn with-bindings*
"Takes a map of Var/value pairs. Installs for the given Vars the
associated values as thread-local bindings. Then calls f with the
11.1. CORE.CLJ 1205
(defmacro with-bindings
"Takes a map of Var/value pairs. Installs for the given Vars the
associated values as thread-local bindings. The executes body.
Pops the installed bindings after body was evaluated. Returns the
value of body."
{:added "1.1"}
[binding-map & body]
(with-bindings* ~binding-map (fn [] ~@body)))
(defn bound-fn*
"Returns a function, which will install the same bindings in effect
as in the thread at the time bound-fn* was called and then call f
with any given arguments. This may be used to define a helper
function which runs on a different thread, but needs the same
bindings in place."
{:added "1.1"
:static true}
[f]
(let [bindings (get-thread-bindings)]
(fn [& args]
(apply with-bindings* bindings f args))))
(defmacro bound-fn
"Returns a function defined by the given fntail, which will install
the same bindings in effect as in the thread at the time bound-fn
was called. This may be used to define a helper function which
runs on a different thread, but needs the same bindings in place."
{:added "1.1"}
[& fntail]
(bound-fn* (fn ~@fntail)))
(defn find-var
"Returns the global var named by the namespace-qualified symbol, or
nil if no var with that name."
{:added "1.0"
:static true}
[sym] (. clojure.lang.Var (find sym)))
(defn binding-conveyor-fn
1206 CHAPTER 11. CLJ/CLOJURE/
{:private true
:added "1.3"}
[f]
(let [frame (clojure.lang.Var/getThreadBindingFrame)]
(fn
([]
(clojure.lang.Var/resetThreadBindingFrame frame)
(f))
([x]
(clojure.lang.Var/resetThreadBindingFrame frame)
(f x))
([x y]
(clojure.lang.Var/resetThreadBindingFrame frame)
(f x y))
([x y z]
(clojure.lang.Var/resetThreadBindingFrame frame)
(f x y z))
([x y z & args]
(clojure.lang.Var/resetThreadBindingFrame frame)
(apply f x y z args)))))
(defn agent
"Creates and returns an agent with an initial value of state and
zero or more options (in any order):
:meta metadata-map
:validator validate-fn
:error-handler handler-fn
:error-mode mode-keyword
(defn send
"Dispatch an action to an agent. Returns the agent immediately.
Subsequently, in a thread from a thread pool, the state of the agent
will be set to the value of:
(defn send-off
"Dispatch a potentially blocking action to an agent. Returns the
agent immediately. Subsequently, in a separate thread, the state of
the agent will be set to the value of:
(defn release-pending-sends
"Normally, actions sent directly or indirectly during another action
are held until the action completes (changes the agents
state). This function can be used to dispatch any pending sent
actions immediately. This has no impact on actions sent during a
transaction, which are still held until commit. If no action is
occurring, does nothing. Returns the number of actions dispatched."
{:added "1.0"
:static true}
[] (clojure.lang.Agent/releasePendingSends))
(defn add-watch
1208 CHAPTER 11. CLJ/CLOJURE/
(defn remove-watch
"Alpha - subject to change.
Removes a watch (set by add-watch) from a reference"
{:added "1.0"
:static true}
[^clojure.lang.IRef reference key]
(.removeWatch reference key))
(defn agent-error
"Returns the exception thrown during an asynchronous action of the
agent if the agent is failed. Returns nil if the agent is not
failed."
{:added "1.2"
:static true}
[^clojure.lang.Agent a] (.getError a))
(defn restart-agent
"When an agent is failed, changes the agent state to new-state and
then un-fails the agent so that sends are allowed again. If
a :clear-actions true option is given, any actions queued on the
agent that were being held while it was failed will be discarded,
otherwise those held actions will proceed. The new-state must pass
the validator if any, or restart will throw an exception and the
agent will remain failed with its old state and error. Watchers, if
any, will NOT be notified of the new state. Throws an exception if
the agent is not failed."
{:added "1.2"
:static true
}
[^clojure.lang.Agent a, new-state & options]
(let [opts (apply hash-map options)]
(.restart a new-state (if (:clear-actions opts) true false))))
11.1. CORE.CLJ 1209
(defn set-error-handler!
"Sets the error-handler of agent a to handler-fn. If an action
being run by the agent throws an exception or doesnt pass the
validator fn, handler-fn will be called with two arguments: the
agent and the exception."
{:added "1.2"
:static true}
[^clojure.lang.Agent a, handler-fn]
(.setErrorHandler a handler-fn))
(defn error-handler
"Returns the error-handler of agent a, or nil if there is none.
See set-error-handler!"
{:added "1.2"
:static true}
[^clojure.lang.Agent a]
(.getErrorHandler a))
(defn set-error-mode!
"Sets the error-mode of agent a to mode-keyword, which must be
either :fail or :continue. If an action being run by the agent
throws an exception or doesnt pass the validator fn, an
error-handler may be called (see set-error-handler!), after which,
if the mode is :continue, the agent will continue as if neither the
action that caused the error nor the error itself ever happened.
If the mode is :fail, the agent will become failed and will stop
accepting new send and send-off actions, and any previously
queued actions will be held until a restart-agent. Deref will
still work, returning the state of the agent before the error."
{:added "1.2"
:static true}
[^clojure.lang.Agent a, mode-keyword]
(.setErrorMode a mode-keyword))
(defn error-mode
"Returns the error-mode of agent a. See set-error-mode!"
{:added "1.2"
:static true}
[^clojure.lang.Agent a]
(.getErrorMode a))
(defn agent-errors
"DEPRECATED: Use agent-error instead.
Returns a sequence of the exceptions thrown during asynchronous
actions of the agent."
{:added "1.0"
:deprecated "1.2"}
[a]
1210 CHAPTER 11. CLJ/CLOJURE/
(defn clear-agent-errors
"DEPRECATED: Use restart-agent instead.
Clears any exceptions thrown during asynchronous actions of the
agent, allowing subsequent actions to occur."
{:added "1.0"
:deprecated "1.2"}
[^clojure.lang.Agent a] (restart-agent a (.deref a)))
(defn shutdown-agents
"Initiates a shutdown of the thread pools that back the agent
system. Running actions will complete, but no new actions will be
accepted"
{:added "1.0"
:static true}
[] (. clojure.lang.Agent shutdown))
(defn ref
"Creates and returns a Ref with an initial value of x and zero or
more options (in any order):
:meta metadata-map
:validator validate-fn
:min-history (default 0)
:max-history (default 10)
(defn deref
"Also reader macro: @ref/@agent/@var/@atom/@delay/@future.
Within a transaction, returns the in-transaction-value of ref,
else returns the most-recently-committed value of ref. When
applied to a var, agent or atom, returns its current state.
When applied to a delay, forces it if not already forced. When
applied to a future, will block if computation not complete"
{:added "1.0"
:static true}
[^clojure.lang.IDeref ref] (.deref ref))
(defn atom
"Creates and returns an Atom with an initial value of x and zero or
more options (in any order):
:meta metadata-map
:validator validate-fn
(defn swap!
"Atomically swaps the value of atom to be:
(apply f current-value-of-atom args). Note that f may be called
multiple times, and thus should be free of side effects. Returns
the value that was swapped in."
{:added "1.0"
:static true}
([^clojure.lang.Atom atom f] (.swap atom f))
([^clojure.lang.Atom atom f x] (.swap atom f x))
([^clojure.lang.Atom atom f x y] (.swap atom f x y))
([^clojure.lang.Atom atom f x y & args] (.swap atom f x y args)))
(defn compare-and-set!
"Atomically sets the value of atom to newval if and only if the
current value of the atom is identical to oldval. Returns true if
set happened, else false"
1212 CHAPTER 11. CLJ/CLOJURE/
{:added "1.0"
:static true}
[^clojure.lang.Atom atom oldval newval]
(.compareAndSet atom oldval newval))
(defn reset!
"Sets the value of atom to newval without regard for the
current value. Returns newval."
{:added "1.0"
:static true}
[^clojure.lang.Atom atom newval] (.reset atom newval))
(defn set-validator!
"Sets the validator-fn for a var/ref/agent/atom. validator-fn
must be nil or a side-effect-free fn of one argument, which will
be passed the intended new state on any state change. If the new
state is unacceptable, the validator-fn should return false or
throw an exception. If the current state (root value if var)
is not acceptable to the new validator, an exception will be
thrown and the validator will not be changed."
{:added "1.0"
:static true}
[^clojure.lang.IRef iref validator-fn]
(. iref (setValidator validator-fn)))
(defn get-validator
"Gets the validator-fn for a var/ref/agent/atom."
{:added "1.0"
:static true}
[^clojure.lang.IRef iref] (. iref (getValidator)))
(defn alter-meta!
"Atomically sets the metadata for a namespace/var/ref/agent/atom to be:
(defn reset-meta!
"Atomically resets the metadata for a namespace/var/ref/agent/atom"
{:added "1.0"
:static true}
[^clojure.lang.IReference iref metadata-map]
(.resetMeta iref metadata-map))
(defn commute
"Must be called in a transaction. Sets the in-transaction-value of
11.1. CORE.CLJ 1213
ref to:
At the commit point of the transaction, sets the value of ref to be:
Thus fun should be commutative, or, failing that, you must accept
last-one-in-wins behavior. commute allows for more concurrency than
ref-set."
{:added "1.0"
:static true}
(defn alter
"Must be called in a transaction. Sets the in-transaction-value of
ref to:
(defn ref-set
"Must be called in a transaction. Sets the value of ref.
Returns val."
{:added "1.0"
:static true}
[^clojure.lang.Ref ref val]
(. ref (set val)))
(defn ref-history-count
"Returns the history count of a ref"
{:added "1.1"
:static true}
[^clojure.lang.Ref ref]
(.getHistoryCount ref))
(defn ref-min-history
"Gets the min-history of a ref, or sets it and returns the ref"
{:added "1.1"
:static true}
1214 CHAPTER 11. CLJ/CLOJURE/
([^clojure.lang.Ref ref]
(.getMinHistory ref))
([^clojure.lang.Ref ref n]
(.setMinHistory ref n)))
(defn ref-max-history
"Gets the max-history of a ref, or sets it and returns the ref"
{:added "1.1"
:static true}
([^clojure.lang.Ref ref]
(.getMaxHistory ref))
([^clojure.lang.Ref ref n]
(.setMaxHistory ref n)))
(defn ensure
"Must be called in a transaction. Protects the ref from modification
by other transactions. Returns the in-transaction-value of
ref. Allows for more concurrency than (ref-set ref @ref)"
{:added "1.0"
:static true}
[^clojure.lang.Ref ref]
(. ref (touch))
(. ref (deref)))
(defmacro sync
"transaction-flags => TBD, pass nil for now
(defmacro io!
"If an io! block occurs in a transaction, throws an
IllegalStateException, else runs body in an implicit do. If the
first expression in body is a literal string, will use that as the
exception message."
{:added "1.0"}
[& body]
(let [message (when (string? (first body)) (first body))
body (if message (next body) body)]
(if (clojure.lang.LockingTransaction/isRunning)
(throw
(new IllegalStateException ~(or message "I/O in transaction")))
11.1. CORE.CLJ 1215
(do ~@body))))
(defn comp
"Takes a set of functions and returns a fn that is the composition
of those fns. The returned fn takes a variable number of args,
applies the rightmost of fns to the args, the next
fn (right-to-left) to the result, etc."
{:added "1.0"
:static true}
([] identity)
([f] f)
([f g]
(fn
([] (f (g)))
([x] (f (g x)))
([x y] (f (g x y)))
([x y z] (f (g x y z)))
([x y z & args] (f (apply g x y z args)))))
([f g h]
(fn
([] (f (g (h))))
([x] (f (g (h x))))
([x y] (f (g (h x y))))
([x y z] (f (g (h x y z))))
([x y z & args] (f (g (apply h x y z args))))))
([f1 f2 f3 & fs]
(let [fs (reverse (list* f1 f2 f3 fs))]
(fn [& args]
(loop [ret (apply (first fs) args) fs (next fs)]
(if fs
(recur ((first fs) ret) (next fs))
ret))))))
(defn juxt
"Alpha - name subject to change.
Takes a set of functions and returns a fn that is the juxtaposition
of those fns. The returned fn takes a variable number of args, and
returns a vector containing the result of applying each fn to the
args (left-to-right).
((juxt a b c) x) => [(a x) (b x) (c x)]"
{:added "1.1"
:static true}
([f]
(fn
([] [(f)])
([x] [(f x)])
([x y] [(f x y)])
1216 CHAPTER 11. CLJ/CLOJURE/
(defn partial
"Takes a function f and fewer than the normal arguments to f, and
returns a fn that takes a variable number of additional args. When
called, the returned function calls f with args + additional args."
{:added "1.0"
:static true}
([f arg1]
(fn [& args] (apply f arg1 args)))
([f arg1 arg2]
(fn [& args] (apply f arg1 arg2 args)))
([f arg1 arg2 arg3]
(fn [& args] (apply f arg1 arg2 arg3 args)))
([f arg1 arg2 arg3 & more]
(fn [& args] (apply f arg1 arg2 arg3 (concat more args)))))
[coll]
(if (seq? coll) coll
(or (seq coll) ())))
(defn every?
"Returns true if (pred x) is logical true for every x in coll, else
false."
{:tag Boolean
:added "1.0"
:static true}
[pred coll]
(cond
(nil? (seq coll)) true
(pred (first coll)) (recur pred (next coll))
:else false))
(def
^{:tag Boolean
:doc "Returns false if (pred x) is logical true for every x in
coll, else true."
:arglists ([pred coll])
:added "1.0"}
not-every? (comp not every?))
(defn some
"Returns the first logical true value of (pred x) for any x in coll,
else nil. One common idiom is to use a set as pred, for example
this will return :fred if :fred is in the sequence, otherwise nil:
(some #{:fred} coll)"
{:added "1.0"
:static true}
[pred coll]
(when (seq coll)
(or (pred (first coll)) (recur pred (next coll)))))
(def
^{:tag Boolean
:doc "Returns false if (pred x) is logical true for any x in coll,
else true."
:arglists ([pred coll])
:added "1.0"}
not-any? (comp not some))
(defn map
"Returns a lazy sequence consisting of the result of applying f to the
set of first items of each coll, followed by applying f to the set
of second items in each coll, until any one of the colls is
exhausted. Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments."
{:added "1.0"
:static true}
([f coll]
(lazy-seq
(when-let [s (seq coll)]
(if (chunked-seq? s)
(let [c (chunk-first s)
size (int (count c))
b (chunk-buffer size)]
(dotimes [i size]
(chunk-append b (f (.nth c i))))
(chunk-cons (chunk b) (map f (chunk-rest s))))
(cons (f (first s)) (map f (rest s)))))))
([f c1 c2]
(lazy-seq
(let [s1 (seq c1) s2 (seq c2)]
(when (and s1 s2)
(cons (f (first s1) (first s2))
(map f (rest s1) (rest s2)))))))
([f c1 c2 c3]
(lazy-seq
(let [s1 (seq c1) s2 (seq c2) s3 (seq c3)]
(when (and s1 s2 s3)
(cons (f (first s1) (first s2) (first s3))
(map f (rest s1) (rest s2) (rest s3)))))))
([f c1 c2 c3 & colls]
(let [step (fn step [cs]
(lazy-seq
(let [ss (map seq cs)]
(when (every? identity ss)
(cons (map first ss) (step (map rest ss)))))))]
(map #(apply f %) (step (conj colls c3 c2 c1))))))
(defn mapcat
"Returns the result of applying concat to the result of applying map
11.1. CORE.CLJ 1219
(defn filter
"Returns a lazy sequence of the items in coll for which
(pred item) returns true. pred must be free of side-effects."
{:added "1.0"
:static true}
([pred coll]
(lazy-seq
(when-let [s (seq coll)]
(if (chunked-seq? s)
(let [c (chunk-first s)
size (count c)
b (chunk-buffer size)]
(dotimes [i size]
(when (pred (.nth c i))
(chunk-append b (.nth c i))))
(chunk-cons (chunk b) (filter pred (chunk-rest s))))
(let [f (first s) r (rest s)]
(if (pred f)
(cons f (filter pred r))
(filter pred r))))))))
(defn remove
"Returns a lazy sequence of the items in coll for which
(pred item) returns false. pred must be free of side-effects."
{:added "1.0"
:static true}
[pred coll]
(filter (complement pred) coll))
(defn take
"Returns a lazy sequence of the first n items in coll, or all items if
there are fewer than n."
{:added "1.0"
:static true}
[n coll]
(lazy-seq
(when (pos? n)
(when-let [s (seq coll)]
(cons (first s) (take (dec n) (rest s)))))))
(defn take-while
"Returns a lazy sequence of successive items from coll while
(pred item) returns true. pred must be free of side-effects."
1220 CHAPTER 11. CLJ/CLOJURE/
{:added "1.0"
:static true}
[pred coll]
(lazy-seq
(when-let [s (seq coll)]
(when (pred (first s))
(cons (first s) (take-while pred (rest s)))))))
(defn drop
"Returns a lazy sequence of all but the first n items in coll."
{:added "1.0"
:static true}
[n coll]
(let [step (fn [n coll]
(let [s (seq coll)]
(if (and (pos? n) s)
(recur (dec n) (rest s))
s)))]
(lazy-seq (step n coll))))
(defn drop-last
"Return a lazy sequence of all but the last n (default 1) items
in coll"
{:added "1.0"
:static true}
([s] (drop-last 1 s))
([n s] (map (fn [x _] x) s (drop n s))))
(defn take-last
"Returns a seq of the last n items in coll. Depending on the type
of coll may be no better than linear time. For vectors,
see also subvec."
{:added "1.1"
:static true}
[n coll]
(loop [s (seq coll), lead (seq (drop n coll))]
(if lead
(recur (next s) (next lead))
s)))
(defn drop-while
"Returns a lazy sequence of the items in coll starting from the first
item for which (pred item) returns nil."
{:added "1.0"
:static true}
[pred coll]
(let [step (fn [pred coll]
(let [s (seq coll)]
(if (and s (pred (first s)))
(recur pred (rest s))
11.1. CORE.CLJ 1221
s)))]
(lazy-seq (step pred coll))))
(defn cycle
"Returns a lazy (infinite!) sequence of repetitions of the items
in coll."
{:added "1.0"
:static true}
[coll] (lazy-seq
(when-let [s (seq coll)]
(concat s (cycle s)))))
(defn split-at
"Returns a vector of [(take n coll) (drop n coll)]"
{:added "1.0"
:static true}
[n coll]
[(take n coll) (drop n coll)])
(defn split-with
"Returns a vector of [(take-while pred coll) (drop-while pred coll)]"
{:added "1.0"
:static true}
[pred coll]
[(take-while pred coll) (drop-while pred coll)])
(defn repeat
"Returns a lazy (infinite!, or length n if supplied) sequence of xs."
{:added "1.0"
:static true}
([x] (lazy-seq (cons x (repeat x))))
([n x] (take n (repeat x))))
(defn replicate
"DEPRECATED: Use repeat instead.
Returns a lazy seq of n xs."
{:added "1.0"
:deprecated "1.3"}
[n x] (take n (repeat x)))
(defn iterate
"Returns a lazy sequence of x, (f x), (f (f x)) etc.
f must be free of side-effects"
{:added "1.0"
:static true}
[f x] (cons x (lazy-seq (iterate f (f x)))))
(defn range
"Returns a lazy seq of nums from start (inclusive) to end
(exclusive), by step, where start defaults to 0, step to 1, and end
1222 CHAPTER 11. CLJ/CLOJURE/
to infinity."
{:added "1.0"
:static true}
([] (range 0 Double/POSITIVE_INFINITY 1))
([end] (range 0 end 1))
([start end] (range start end 1))
([start end step]
(lazy-seq
(let [b (chunk-buffer 32)
comp (if (pos? step) < >)]
(loop [i start]
(if (and (< (count b) 32)
(comp i end))
(do
(chunk-append b i)
(recur (+ i step)))
(chunk-cons (chunk b)
(when (comp i end)
(range i end step)))))))))
(defn merge
"Returns a map that consists of the rest of the maps conj-ed onto
the first. If a key occurs in more than one map, the mapping from
the latter (left-to-right) will be the mapping in the result."
{:added "1.0"
:static true}
[& maps]
(when (some identity maps)
(reduce1 #(conj (or %1 {}) %2) maps)))
(defn merge-with
"Returns a map that consists of the rest of the maps conj-ed onto
the first. If a key occurs in more than one map, the mapping(s)
from the latter (left-to-right) will be combined with the mapping in
the result by calling (f val-in-result val-in-latter)."
{:added "1.0"
:static true}
[f & maps]
(when (some identity maps)
(let [merge-entry (fn [m e]
(let [k (key e) v (val e)]
(if (contains? m k)
(assoc m k (f (get m k) v))
(assoc m k v))))
merge2 (fn [m1 m2]
(reduce1 merge-entry (or m1 {}) (seq m2)))]
(reduce1 merge2 maps))))
11.1. CORE.CLJ 1223
(defn zipmap
"Returns a map with the keys mapped to the corresponding vals."
{:added "1.0"
:static true}
[keys vals]
(loop [map {}
ks (seq keys)
vs (seq vals)]
(if (and ks vs)
(recur (assoc map (first ks) (first vs))
(next ks)
(next vs))
map)))
(defmacro declare
"defs the supplied var names with no bindings,
useful for making forward declarations."
{:added "1.0"}
[& names]
(do
~@(map #(list def (vary-meta % assoc :declared true)) names)))
(defn line-seq
"Returns the lines of text from rdr as a lazy sequence of strings.
rdr must implement java.io.BufferedReader."
{:added "1.0"
:static true}
[^java.io.BufferedReader rdr]
(when-let [line (.readLine rdr)]
(cons line (lazy-seq (line-seq rdr)))))
(defn comparator
"Returns an implementation of java.util.Comparator based upon pred."
{:added "1.0"
:static true}
[pred]
(fn [x y]
(cond (pred x y) -1 (pred y x) 1 :else 0)))
(defn sort
"Returns a sorted sequence of the items in coll. If no comparator is
supplied, uses compare. comparator must
implement java.util.Comparator."
{:added "1.0"
:static true}
([coll]
(sort compare coll))
([^java.util.Comparator comp coll]
(if (seq coll)
(let [a (to-array coll)]
1224 CHAPTER 11. CLJ/CLOJURE/
(defn sort-by
"Returns a sorted sequence of the items in coll, where the sort
order is determined by comparing (keyfn item). If no comparator is
supplied, uses compare. comparator must
implement java.util.Comparator."
{:added "1.0"
:static true}
([keyfn coll]
(sort-by keyfn compare coll))
([keyfn ^java.util.Comparator comp coll]
(sort (fn [x y] (. comp (compare (keyfn x) (keyfn y)))) coll)))
(defn partition
"Returns a lazy sequence of lists of n items each, at offsets step
apart. If step is not supplied, defaults to n, i.e. the partitions
do not overlap. If a pad collection is supplied, use its elements
as necessary to complete last partition upto n items. In case
there are not enough padding elements, return a partition with
less than n items."
{:added "1.0"
:static true}
([n coll]
(partition n n coll))
([n step coll]
(lazy-seq
(when-let [s (seq coll)]
(let [p (take n s)]
(when (= n (count p))
(cons p (partition n step (drop step s))))))))
([n step pad coll]
(lazy-seq
(when-let [s (seq coll)]
(let [p (take n s)]
(if (= n (count p))
(cons p (partition n step pad (drop step s)))
(list (take n (concat p pad)))))))))
;; evaluation
(defn eval
"Evaluates the form data structure (not text!) and returns the result."
{:added "1.0"
:static true}
[form] (. clojure.lang.Compiler (eval form)))
(defmacro doseq
11.1. CORE.CLJ 1225
(defn dorun
"When lazy sequences are produced via functions that have side
effects, any effects other than those needed to produce the first
element in the seq do not occur until the seq is consumed. dorun can
be used to force any effects. Walks through the successive nexts of
the seq, does not retain the head and returns nil."
{:added "1.0"
:static true}
([coll]
(when (seq coll)
(recur (next coll))))
([n coll]
(when (and (seq coll) (pos? n))
(recur (dec n) (next coll)))))
(defn doall
"When lazy sequences are produced via functions that have side
effects, any effects other than those needed to produce the first
element in the seq do not occur until the seq is consumed. doall can
be used to force any effects. Walks through the successive nexts of
the seq, retains the head and returns it, thus causing the entire
seq to reside in memory at one time."
{:added "1.0"
:static true}
([coll]
(dorun coll)
coll)
([n coll]
(dorun n coll)
coll))
(defn await
"Blocks the current thread (indefinitely!) until all actions
dispatched thus far, from this thread or agent, to the agent(s) have
occurred. Will block on failed agents. Will never return if
a failed agent is restarted with :clear-actions true."
{:added "1.0"
11.1. CORE.CLJ 1227
:static true}
[& agents]
(io! "await in transaction"
(when *agent*
(throw (new Exception "Cant await in agent action")))
(let [latch (new java.util.concurrent.CountDownLatch (count agents))
count-down (fn [agent] (. latch (countDown)) agent)]
(doseq [agent agents]
(send agent count-down))
(. latch (await)))))
(defn await-for
"Blocks the current thread until all actions dispatched thus
far (from this thread or agent) to the agents have occurred, or the
timeout (in milliseconds) has elapsed. Returns nil if returning due
to timeout, non-nil otherwise."
{:added "1.0"
:static true}
[timeout-ms & agents]
(io! "await-for in transaction"
(when *agent*
(throw (new Exception "Cant await in agent action")))
(let [latch (new java.util.concurrent.CountDownLatch (count agents))
count-down (fn [agent] (. latch (countDown)) agent)]
(doseq [agent agents]
(send agent count-down))
(. latch
(await timeout-ms
(. java.util.concurrent.TimeUnit MILLISECONDS))))))
(defmacro dotimes
"bindings => name n
~@body
(recur (unchecked-inc ~i)))))))
#_(defn into
"Returns a new coll consisting of to-coll with all of the items of
from-coll conjoined."
{:added "1.0"}
[to from]
(let [ret to items (seq from)]
(if items
(recur (conj ret (first items)) (next items))
ret)))
\getchunk{defn persistent!}
\getchunk{defn conj!}
\getchunk{defn assoc!}
\getchunk{defn dissoc!}
\getchunk{defn pop!}
\getchunk{defn disj!}
(defmacro import
"import-list => (package-symbol class-name-symbols*)
(defn into-array
"Returns an array with components set to the values in aseq. The
arrays component type is type if provided, or the type of the
first value in aseq if present, or Object. All values in aseq
must be compatible with the component type. Class objects for
must be compatible with the primitive types can be obtained
using, e.g., Integer/TYPE."
{:added "1.0"
:static true}
([aseq]
(clojure.lang.RT/seqToTypedArray (seq aseq)))
([type aseq]
(clojure.lang.RT/seqToTypedArray type (seq aseq))))
(defn class
"Returns the Class of x"
{:added "1.0"
:static true}
^Class [^Object x] (if (nil? x) x (. x (getClass))))
(defn type
"Returns the :type metadata of x, or its Class if none"
{:added "1.0"
:static true}
[x]
(or (get (meta x) :type) (class x)))
(defn num
"Coerce to Number"
{:tag Number
:inline (fn [x] (. clojure.lang.Numbers (num ~x)))
:added "1.0"}
[x] (. clojure.lang.Numbers (num x)))
(defn long
"Coerce to long"
{:inline (fn [x] (. clojure.lang.RT (longCast ~x)))
:added "1.0"}
[^Number x] (clojure.lang.RT/longCast x))
1230 CHAPTER 11. CLJ/CLOJURE/
(defn float
"Coerce to float"
{:inline (fn [x]
(. clojure.lang.RT
(~(if *unchecked-math* uncheckedFloatCast floatCast) ~x)))
:added "1.0"}
[^Number x] (clojure.lang.RT/floatCast x))
(defn double
"Coerce to double"
{:inline (fn [x] (. clojure.lang.RT (doubleCast ~x)))
:added "1.0"}
[^Number x] (clojure.lang.RT/doubleCast x))
(defn short
"Coerce to short"
{:inline
(fn [x]
(. clojure.lang.RT
(~(if *unchecked-math* uncheckedShortCast shortCast) ~x)))
:added "1.0"}
[^Number x] (clojure.lang.RT/shortCast x))
(defn byte
"Coerce to byte"
{:inline
(fn [x]
(. clojure.lang.RT
(~(if *unchecked-math* uncheckedByteCast byteCast) ~x)))
:added "1.0"}
[^Number x] (clojure.lang.RT/byteCast x))
(defn char
"Coerce to char"
{:inline
(fn [x]
(. clojure.lang.RT
(~(if *unchecked-math* uncheckedCharCast charCast) ~x)))
:added "1.1"}
[x] (. clojure.lang.RT (charCast x)))
(defn boolean
"Coerce to boolean"
{
:inline (fn [x] (. clojure.lang.RT (booleanCast ~x)))
:added "1.0"}
[x] (clojure.lang.RT/booleanCast x))
(defn unchecked-byte
11.1. CORE.CLJ 1231
(defn unchecked-short
"Coerce to short. Subject to rounding or truncation."
{:inline (fn [x] (. clojure.lang.RT (uncheckedShortCast ~x)))
:added "1.3"}
[^Number x] (clojure.lang.RT/uncheckedShortCast x))
(defn unchecked-char
"Coerce to char. Subject to rounding or truncation."
{:inline (fn [x] (. clojure.lang.RT (uncheckedCharCast ~x)))
:added "1.3"}
[x] (. clojure.lang.RT (uncheckedCharCast x)))
(defn unchecked-int
"Coerce to int. Subject to rounding or truncation."
{:inline (fn [x] (. clojure.lang.RT (uncheckedIntCast ~x)))
:added "1.3"}
[^Number x] (clojure.lang.RT/uncheckedIntCast x))
(defn unchecked-long
"Coerce to long. Subject to rounding or truncation."
{:inline (fn [x] (. clojure.lang.RT (uncheckedLongCast ~x)))
:added "1.3"}
[^Number x] (clojure.lang.RT/uncheckedLongCast x))
(defn unchecked-float
"Coerce to float. Subject to rounding."
{:inline (fn [x] (. clojure.lang.RT (uncheckedFloatCast ~x)))
:added "1.3"}
[^Number x] (clojure.lang.RT/uncheckedFloatCast x))
(defn unchecked-double
"Coerce to double. Subject to rounding."
{:inline (fn [x] (. clojure.lang.RT (uncheckedDoubleCast ~x)))
:added "1.3"}
[^Number x] (clojure.lang.RT/uncheckedDoubleCast x))
(defn number?
"Returns true if x is a Number"
{:added "1.0"
:static true}
[x]
(instance? Number x))
(defn integer?
1232 CHAPTER 11. CLJ/CLOJURE/
(defn mod
"Modulus of num and div. Truncates toward negative infinity."
{:added "1.0"
:static true}
[num div]
(let [m (rem num div)]
(if (or (zero? m) (pos? (* num div)))
m
(+ m div))))
(defn ratio?
"Returns true if n is a Ratio"
{:added "1.0"
:static true}
[n] (instance? clojure.lang.Ratio n))
(defn numerator
"Returns the numerator part of a Ratio."
{:tag BigInteger
:added "1.2"
:static true}
[r]
(.numerator ^clojure.lang.Ratio r))
(defn denominator
"Returns the denominator part of a Ratio."
{:tag BigInteger
:added "1.2"
:static true}
[r]
(.denominator ^clojure.lang.Ratio r))
(defn decimal?
"Returns true if n is a BigDecimal"
{:added "1.0"
:static true}
[n] (instance? BigDecimal n))
(defn float?
11.1. CORE.CLJ 1233
(defn bigint
"Coerce to BigInt"
{:tag clojure.lang.BigInt
:static true
:added "1.3"}
[x] (cond
(instance? clojure.lang.BigInt x) x
(instance? BigInteger x) (clojure.lang.BigInt/fromBigInteger x)
(decimal? x) (bigint (.toBigInteger ^BigDecimal x))
(ratio? x) (bigint (.bigIntegerValue ^clojure.lang.Ratio x))
(number? x) (clojure.lang.BigInt/valueOf (long x))
:else (bigint (BigInteger. x))))
(defn biginteger
"Coerce to BigInteger"
{:tag BigInteger
:added "1.0"
:static true}
[x] (cond
(instance? BigInteger x) x
(instance? clojure.lang.BigInt x)
(.toBigInteger ^clojure.lang.BigInt x)
(decimal? x) (.toBigInteger ^BigDecimal x)
(ratio? x) (.bigIntegerValue ^clojure.lang.Ratio x)
(number? x) (BigInteger/valueOf (long x))
:else (BigInteger. x)))
(defn bigdec
"Coerce to BigDecimal"
{:tag BigDecimal
:added "1.0"
:static true}
[x] (cond
(decimal? x) x
(float? x) (. BigDecimal valueOf (double x))
(ratio? x) (/ (BigDecimal. (.numerator x)) (.denominator x))
(instance? BigInteger x) (BigDecimal. ^BigInteger x)
1234 CHAPTER 11. CLJ/CLOJURE/
(defn pr-on
{:private true
:static true}
[x w]
(if *print-dup*
(print-dup x w)
(print-method x w))
nil)
(defn pr
"Prints the object(s) to the output stream that is the current value
of *out*. Prints the object(s), separated by spaces if there is
more than one. By default, pr and prn print in a way that objects
can be read by the reader"
{:dynamic true
:added "1.0"}
([] nil)
([x]
(pr-on x *out*))
([x & more]
(pr x)
(. *out* (append \space))
(if-let [nmore (next more)]
(recur (first more) nmore)
(apply pr more))))
(defn newline
"Writes a platform-specific newline to *out*"
{:added "1.0"
:static true}
[]
(. *out* (append system-newline))
nil)
(defn flush
"Flushes the output stream that is the current value of
*out*"
{:added "1.0"
:static true}
11.1. CORE.CLJ 1235
[]
(. *out* (flush))
nil)
(defn prn
"Same as pr followed by (newline). Observes *flush-on-newline*"
{:added "1.0"
:static true}
[& more]
(apply pr more)
(newline)
(when *flush-on-newline*
(flush)))
(defn print
"Prints the object(s) to the output stream that is the current value
of *out*. print and println produce output for human consumption."
{:added "1.0"
:static true}
[& more]
(binding [*print-readably* nil]
(apply pr more)))
(defn println
"Same as print followed by (newline)"
{:added "1.0"
:static true}
[& more]
(binding [*print-readably* nil]
(apply prn more)))
(defn read
"Reads the next object from stream, which must be an instance of
java.io.PushbackReader or some derivee. stream defaults to the
current value of *in* ."
{:added "1.0"
:static true}
([]
(read *in*))
([stream]
(read stream true nil))
([stream eof-error? eof-value]
(read stream eof-error? eof-value false))
([stream eof-error? eof-value recursive?]
(. clojure.lang.LispReader
(read stream (boolean eof-error?) eof-value recursive?))))
(defn read-line
"Reads the next line from stream that is the current value of *in* ."
{:added "1.0"
1236 CHAPTER 11. CLJ/CLOJURE/
:static true}
[]
(if (instance? clojure.lang.LineNumberingPushbackReader *in*)
(.readLine ^clojure.lang.LineNumberingPushbackReader *in*)
(.readLine ^java.io.BufferedReader *in*)))
(defn read-string
"Reads one object from the string s"
{:added "1.0"
:static true}
[s] (clojure.lang.RT/readString s))
(defn subvec
"Returns a persistent vector of the items in vector from
start (inclusive) to end (exclusive). If end is not supplied,
defaults to (count vector). This operation is O(1) and very fast, as
the resulting vector shares structure with the original and no
trimming is done."
{:added "1.0"
:static true}
([v start]
(subvec v start (count v)))
([v start end]
(. clojure.lang.RT (subvec v start end))))
(defmacro with-open
"bindings => [name init ...]
(defmacro doto
"Evaluates x then calls all of the methods and functions with the
value of x supplied at the front of the given arguments. The forms
11.1. CORE.CLJ 1237
(defmacro memfn
"Expands into code that creates a fn that expects to be passed an
object and any args and calls the named instance method on the
object passing the args. Use when you want to treat a Java method as
a first-class fn."
{:added "1.0"}
[name & args]
(fn [target# ~@args]
(. target# (~name ~@args))))
(defmacro time
"Evaluates expr and prints the time it took. Returns the value of
expr."
{:added "1.0"}
[expr]
(let [start# (. System (nanoTime))
ret# ~expr]
(prn (str "Elapsed time: "
(/ (double (- (. System (nanoTime)) start#)) 1000000.0)
" msecs"))
ret#))
(defn alength
"Returns the length of the Java array. Works on arrays of all
types."
{:inline (fn [a] (. clojure.lang.RT (alength ~a)))
:added "1.0"}
[array] (. clojure.lang.RT (alength array)))
(defn aclone
"Returns a clone of the Java array. Works on arrays of known
types."
1238 CHAPTER 11. CLJ/CLOJURE/
(defn aget
"Returns the value at the index/indices. Works on Java arrays of all
types."
{:inline (fn [a i] (. clojure.lang.RT (aget ~a (int ~i))))
:inline-arities #{2}
:added "1.0"}
([array idx]
(clojure.lang.Reflector/prepRet
(.getComponentType (class array)) (. Array (get array idx))))
([array idx & idxs]
(apply aget (aget array idx) idxs)))
(defn aset
"Sets the value at the index/indices. Works on Java arrays of
reference types. Returns val."
{:inline (fn [a i v] (. clojure.lang.RT (aset ~a (int ~i) ~v)))
:inline-arities #{3}
:added "1.0"}
([array idx val]
(. Array (set array idx val))
val)
([array idx idx2 & idxv]
(apply aset (aget array idx) idx2 idxv)))
(defmacro
^{:private true}
def-aset [name method coerce]
(defn ~name
{:arglists
([~array ~idx ~val] [~array ~idx ~idx2 & ~idxv])}
([array# idx# val#]
(. Array (~method array# idx# (~coerce val#)))
val#)
([array# idx# idx2# & idxv#]
(apply ~name (aget array# idx#) idx2# idxv#))))
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of int. Returns val."
:added "1.0"}
aset-int setInt int)
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of long. Returns val."
:added "1.0"}
11.1. CORE.CLJ 1239
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of boolean. Returns val."
:added "1.0"}
aset-boolean setBoolean boolean)
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of float. Returns val."
:added "1.0"}
aset-float setFloat float)
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of double. Returns val."
:added "1.0"}
aset-double setDouble double)
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of short. Returns val."
:added "1.0"}
aset-short setShort short)
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of byte. Returns val."
:added "1.0"}
aset-byte setByte byte)
(def-aset
^{:doc "Sets the value at the index/indices.
Works on arrays of char. Returns val."
:added "1.0"}
aset-char setChar char)
(defn make-array
"Creates and returns an array of instances of the specified class of
the specified dimension(s). Note that a class object is required.
Class objects can be obtained by using their imported or
fully-qualified name. Class objects for the primitive types can be
obtained using, e.g., Integer/TYPE."
{:added "1.0"
:static true}
([^Class type len]
(. Array (newInstance type (int len))))
([^Class type dim & more-dims]
(let [dims (cons dim more-dims)
1240 CHAPTER 11. CLJ/CLOJURE/
(defn to-array-2d
"Returns a (potentially-ragged) 2-dimensional array of Objects
containing the contents of coll, which can be any Collection of any
Collection."
{:tag "[[Ljava.lang.Object;"
:added "1.0"
:static true}
[^java.util.Collection coll]
(let
[ret
(make-array
(. Class (forName "[Ljava.lang.Object;"))
(. coll (size)))]
(loop [i 0 xs (seq coll)]
(when xs
(aset ret i (to-array (first xs)))
(recur (inc i) (next xs))))
ret))
(defn macroexpand-1
"If form represents a macro form, returns its expansion,
else returns form."
{:added "1.0"
:static true}
[form]
(. clojure.lang.Compiler (macroexpand1 form)))
(defn macroexpand
"Repeatedly calls macroexpand-1 on form until it no longer
represents a macro form, then returns it. Note neither
macroexpand-1 nor macroexpand expand macros in subforms."
{:added "1.0"
:static true}
[form]
(let [ex (macroexpand-1 form)]
(if (identical? ex form)
form
(macroexpand ex))))
(defn create-struct
"Returns a structure basis object."
{:added "1.0"
:static true}
[& keys]
(. clojure.lang.PersistentStructMap (createSlotMap keys)))
11.1. CORE.CLJ 1241
(defmacro defstruct
"Same as (def name (create-struct keys...))"
{:added "1.0"
:static true}
[name & keys]
(def ~name (create-struct ~@keys)))
(defn struct-map
"Returns a new structmap instance with the keys of the
structure-basis. keyvals may contain all, some or none of the basis
keys - where values are not supplied they will default to nil.
keyvals can also contain keys not in the basis."
{:added "1.0"
:static true}
[s & inits]
(. clojure.lang.PersistentStructMap (create s inits)))
(defn struct
"Returns a new structmap instance with the keys of the
structure-basis. vals must be supplied for basis keys in order -
where values are not supplied they will default to nil."
{:added "1.0"
:static true}
[s & vals]
(. clojure.lang.PersistentStructMap (construct s vals)))
(defn accessor
"Returns a fn that, given an instance of a structmap with the basis,
returns the value at the key. The key must be in the basis. The
returned function should be (slightly) more efficient than using
get, but such use of accessors should be limited to known
performance-critical areas."
{:added "1.0"
:static true}
[s key]
(. clojure.lang.PersistentStructMap (getAccessor s key)))
(defn load-reader
"Sequentially read and evaluate the set of forms contained in the
stream/file"
{:added "1.0"
:static true}
[rdr] (. clojure.lang.Compiler (load rdr)))
(defn load-string
"Sequentially read and evaluate the set of forms contained in the
string"
{:added "1.0"
:static true}
1242 CHAPTER 11. CLJ/CLOJURE/
[s]
(let [rdr (-> (java.io.StringReader. s)
(clojure.lang.LineNumberingPushbackReader.))]
(load-reader rdr)))
(defn set
"Returns a set of the distinct elements of coll."
{:added "1.0"
:static true}
[coll] (clojure.lang.PersistentHashSet/create (seq coll)))
(defn find-ns
"Returns the namespace named by the symbol or nil if it doesnt exist."
{:added "1.0"
:static true}
[sym] (clojure.lang.Namespace/find sym))
(defn create-ns
"Create a new namespace named by the symbol if one doesnt already
exist, returns it or the already-existing namespace of the same
name."
{:added "1.0"
:static true}
[sym] (clojure.lang.Namespace/findOrCreate sym))
(defn remove-ns
"Removes the namespace named by the symbol. Use with caution.
Cannot be used to remove the clojure namespace."
{:added "1.0"
:static true}
[sym] (clojure.lang.Namespace/remove sym))
(defn all-ns
"Returns a sequence of all namespaces."
{:added "1.0"
:static true}
[] (clojure.lang.Namespace/all))
(defn the-ns
"If passed a namespace, returns it. Else, when passed a symbol,
11.1. CORE.CLJ 1243
(defn ns-name
"Returns the name of the namespace, a symbol."
{:added "1.0"
:static true}
[ns]
(.getName (the-ns ns)))
(defn ns-map
"Returns a map of all the mappings for the namespace."
{:added "1.0"
:static true}
[ns]
(.getMappings (the-ns ns)))
(defn ns-unmap
"Removes the mappings for the symbol from the namespace."
{:added "1.0"
:static true}
[ns sym]
(.unmap (the-ns ns) sym))
(defn ns-publics
"Returns a map of the public intern mappings for the namespace."
{:added "1.0"
:static true}
[ns]
(let [ns (the-ns ns)]
(filter-key val
(fn [^clojure.lang.Var v] (and (instance? clojure.lang.Var v)
(= ns (.ns v))
(.isPublic v)))
(ns-map ns))))
(defn ns-imports
"Returns a map of the import mappings for the namespace."
{:added "1.0"
1244 CHAPTER 11. CLJ/CLOJURE/
:static true}
[ns]
(filter-key val (partial instance? Class) (ns-map ns)))
(defn ns-interns
"Returns a map of the intern mappings for the namespace."
{:added "1.0"
:static true}
[ns]
(let [ns (the-ns ns)]
(filter-key val
(fn [^clojure.lang.Var v] (and (instance? clojure.lang.Var v)
(= ns (.ns v))))
(ns-map ns))))
(defn refer
"refers to all public vars of ns, subject to filters.
filters can include at most one each of:
:exclude list-of-symbols
:only list-of-symbols
:rename map-of-fromsymbol-tosymbol
For each public interned var in the namespace named by the symbol,
adds a mapping from the name of the var to the var to the current
namespace. Throws an exception if name is already mapped to
something else in the current namespace. Filters can be used to
select a subset, via inclusion or exclusion, or to provide a mapping
to a symbol different from the vars name, in order to prevent
clashes. Use :use in the ns macro in preference to calling this
directly."
{:added "1.0"}
[ns-sym & filters]
(let [ns (or (find-ns ns-sym)
(throw (new Exception (str "No namespace: " ns-sym))))
fs (apply hash-map filters)
nspublics (ns-publics ns)
rename (or (:rename fs) {})
exclude (set (:exclude fs))
to-do (or (:only fs) (keys nspublics))]
(doseq [sym to-do]
(when-not (exclude sym)
(let [v (nspublics sym)]
(when-not v
(throw (new java.lang.IllegalAccessError
(if (get (ns-interns ns) sym)
(str sym " is not public")
(str sym " does not exist")))))
(. *ns* (refer (or (rename sym) sym) v)))))))
11.1. CORE.CLJ 1245
(defn ns-refers
"Returns a map of the refer mappings for the namespace."
{:added "1.0"
:static true}
[ns]
(let [ns (the-ns ns)]
(filter-key val
(fn [^clojure.lang.Var v] (and (instance? clojure.lang.Var v)
(not= ns (.ns v))))
(ns-map ns))))
(defn alias
"Add an alias in the current namespace to another
namespace. Arguments are two symbols: the alias to be used, and
the symbolic name of the target namespace. Use :as in the ns macro
in preference to calling this directly."
{:added "1.0"
:static true}
[alias namespace-sym]
(.addAlias *ns* alias (the-ns namespace-sym)))
(defn ns-aliases
"Returns a map of the aliases for the namespace."
{:added "1.0"
:static true}
[ns]
(.getAliases (the-ns ns)))
(defn ns-unalias
"Removes the alias for the symbol from the namespace."
{:added "1.0"
:static true}
[ns sym]
(.removeAlias (the-ns ns) sym))
(defn take-nth
"Returns a lazy seq of every nth item in coll."
{:added "1.0"
:static true}
[n coll]
(lazy-seq
(when-let [s (seq coll)]
(cons (first s) (take-nth n (drop n s))))))
(defn interleave
"Returns a lazy seq of the first item in each coll, then the
second etc."
{:added "1.0"
:static true}
([c1 c2]
1246 CHAPTER 11. CLJ/CLOJURE/
(lazy-seq
(let [s1 (seq c1) s2 (seq c2)]
(when (and s1 s2)
(cons (first s1) (cons (first s2)
(interleave (rest s1) (rest s2))))))))
([c1 c2 & colls]
(lazy-seq
(let [ss (map seq (conj colls c2 c1))]
(when (every? identity ss)
(concat (map first ss) (apply interleave (map rest ss))))))))
(defn var-get
"Gets the value in the var object"
{:added "1.0"
:static true}
[^clojure.lang.Var x] (. x (get)))
(defn var-set
"Sets the value in the var object to val. The var must be
thread-locally bound."
{:added "1.0"
:static true}
[^clojure.lang.Var x val] (. x (set val)))
(defmacro with-local-vars
"varbinding=> symbol init-expr
(defn ns-resolve
"Returns the var or Class to which a symbol will be resolved in the
namespace (unless found in the environement), else nil. Note that
if the symbol is fully qualified, the var/Class to which it resolves
11.1. CORE.CLJ 1247
(defn resolve
"same as (ns-resolve *ns* symbol) or (ns-resolve *ns* &env symbol)"
{:added "1.0"
:static true}
([sym] (ns-resolve *ns* sym))
([env sym] (ns-resolve *ns* env sym)))
(defn array-map
"Constructs an array-map."
{:added "1.0"
:static true}
([] (. clojure.lang.PersistentArrayMap EMPTY))
([& keyvals]
(clojure.lang.PersistentArrayMap/createWithCheck
(to-array keyvals))))
(defn nthnext
"Returns the nth next of coll, (seq coll) when n is 0."
{:added "1.0"
:static true}
[coll n]
(loop [n n xs (seq coll)]
(if (and xs (pos? n))
(recur (dec n) (next xs))
xs)))
(= firstb &)
(recur
(pb ret
(second bs)
(list nthnext gvec n))
n
(nnext bs)
true)
(= firstb :as) (pb ret (second bs) gvec)
:else (if seen-rest?
(throw (new Exception
"Unsupported binding form, only :as can follow & parameter"))
(recur
(pb ret firstb
(list nth gvec n nil))
(inc n)
(next bs)
seen-rest?))))
ret))))
pmap
(fn [bvec b v]
(let [gmap (or (:as b) (gensym "map__"))
defaults (:or b)]
(loop [ret (-> bvec (conj gmap) (conj v)
(conj gmap)
(conj
(if (seq? ~gmap)
(apply hash-map ~gmap)
~gmap)))
bes (reduce1
(fn [bes entry]
(reduce1
#(assoc %1 %2 ((val entry) %2))
(dissoc bes (key entry))
((key entry) bes)))
(dissoc b :as :or)
{:keys
#(keyword (str %)),
:strs str,
:syms #(list quote %)})]
(if (seq bes)
(let [bb (key (first bes))
bk (val (first bes))
has-default (contains? defaults bb)]
(recur (pb ret bb
(if has-default
(list get gmap bk (defaults bb))
(list get gmap bk)))
(next bes)))
ret))))]
11.1. CORE.CLJ 1249
(cond
(symbol? b) (-> bvec (conj b) (conj v))
(vector? b) (pvec bvec b v)
(map? b) (pmap bvec b v)
:else
(throw
(new Exception
(str "Unsupported binding form: " b))))))
process-entry (fn [bvec b] (pb bvec (first b) (second b)))]
(if (every? symbol? (map first bents))
bindings
(reduce1 process-entry [] bents))))
(defmacro let
"binding => binding-form init-expr
Defines a function"
{:added "1.0", :special-form true,
:forms
[(fn name? [params* ] exprs*) (fn name? ([params* ] exprs*)+)]}
[& sigs]
(let [name (if (symbol? (first sigs)) (first sigs) nil)
sigs (if name (next sigs) sigs)
sigs (if (vector? (first sigs)) (list sigs) sigs)
psig (fn* [sig]
(let [[params & body] sig
conds (when (and (next body) (map? (first body)))
(first body))
body (if conds (next body) body)
conds (or conds (meta params))
pre (:pre conds)
post (:post conds)
body (if post
((let [~% ~(if (< 1 (count body))
(do ~@body)
(first body))]
~@(map (fn* [c] (assert ~c)) post)
~%))
body)
body (if pre
(concat (map (fn* [c] (assert ~c)) pre)
body)
body)]
(maybe-destructured params body)))
new-sigs (map psig sigs)]
(with-meta
(if name
(list* fn* name new-sigs)
(cons fn* new-sigs))
(meta &form))))
(defmacro loop
"Evaluates the exprs in a lexical context in which the symbols in
the binding-forms are bound to their respective init-exprs or parts
therein. Acts as a recur target."
{:added "1.0", :special-form true, :forms [(loop [bindings*] exprs*)]}
[bindings & body]
(assert-args loop
(vector? bindings) "a vector for its binding"
(even? (count bindings))
"an even number of forms in binding vector")
(let [db (destructure bindings)]
(if (= db bindings)
11.1. CORE.CLJ 1251
(defmacro when-first
"bindings => x xs
(defmacro lazy-cat
"Expands to code which yields a lazy sequence of the concatenation
of the supplied colls. Each coll expr is not evaluated until it is
needed.
(defmacro for
"List comprehension. Takes a vector of one or more
binding-form/collection-expr pairs, each followed by zero or more
modifiers, and yields a lazy sequence of evaluations of expr.
Collections are iterated in a nested fashion, rightmost fastest,
and nested coll-exprs can refer to bindings created in prior
binding-forms. Supported modifiers are: :let [binding-form expr ...],
:while test, :when test.
(take 100
(for [x (range 100000000) y (range 1000000) :while (< y x)]
1252 CHAPTER 11. CLJ/CLOJURE/
[x y]))"
{:added "1.0"}
[seq-exprs body-expr]
(assert-args for
(vector? seq-exprs) "a vector for its binding"
(even? (count seq-exprs))
"an even number of forms in binding vector")
(let [to-groups (fn [seq-exprs]
(reduce1 (fn [groups [k v]]
(if (keyword? k)
(conj (pop groups)
(conj (peek groups) [k v]))
(conj groups [k v])))
[] (partition 2 seq-exprs)))
err (fn [& msg]
(throw
(IllegalArgumentException.
^String (apply str msg))))
emit-bind (fn emit-bind [[[bind expr & mod-pairs]
& [[_ next-expr] :as next-groups]]]
(let [giter (gensym "iter__")
gxs (gensym "s__")
do-mod
(fn do-mod [[[k v :as pair] & etc]]
(cond
(= k :let)
(let ~v ~(do-mod etc))
(= k :while)
(when ~v ~(do-mod etc))
(= k :when) (if ~v
~(do-mod etc)
(recur (rest ~gxs)))
(keyword? k)
(err "Invalid for keyword " k)
next-groups
(let
[iterys#
~(emit-bind next-groups)
fs#
(seq (iterys# ~next-expr))]
(if fs#
(concat fs#
(~giter (rest ~gxs)))
(recur (rest ~gxs))))
:else (cons
~body-expr
(~giter (rest ~gxs)))))]
(if next-groups
#_"not the inner-most loop"
(fn ~giter [~gxs]
11.1. CORE.CLJ 1253
(lazy-seq
(loop [~gxs ~gxs]
(when-first [~bind ~gxs]
~(do-mod mod-pairs)))))
#_"inner-most loop"
(let [gi (gensym "i__")
gb (gensym "b__")
do-cmod
(fn do-cmod [[[k v :as pair] & etc]]
(cond
(= k :let)
(let ~v ~(do-cmod etc))
(= k :while)
(when ~v ~(do-cmod etc))
(= k :when)
(if ~v
~(do-cmod etc)
(recur
(unchecked-inc ~gi)))
(keyword? k)
(err
"Invalid for keyword "
k)
:else
(do
(chunk-append
~gb ~body-expr)
(recur
(unchecked-inc ~gi)))))]
(fn ~giter [~gxs]
(lazy-seq
(loop [~gxs ~gxs]
(when-let [~gxs (seq ~gxs)]
(if (chunked-seq? ~gxs)
(let [c# (chunk-first ~gxs)
size# (int (count c#))
~gb (chunk-buffer size#)]
(if
(loop [~gi (int 0)]
(if (< ~gi size#)
(let
[~bind (.nth c# ~gi)]
~(do-cmod mod-pairs))
true))
(chunk-cons
(chunk ~gb)
(~giter (chunk-rest ~gxs)))
(chunk-cons (chunk ~gb) nil)))
(let [~bind (first ~gxs)]
~(do-mod mod-pairs)))))))))))]
1254 CHAPTER 11. CLJ/CLOJURE/
(defmacro comment
"Ignores body, yields nil"
{:added "1.0"}
[& body])
(defmacro with-out-str
"Evaluates exprs in a context in which *out* is bound to a fresh
StringWriter. Returns the string created by any nested printing
calls."
{:added "1.0"}
[& body]
(let [s# (new java.io.StringWriter)]
(binding [*out* s#]
~@body
(str s#))))
(defmacro with-in-str
"Evaluates body in a context in which *in* is bound to a fresh
StringReader initialized with the string s."
{:added "1.0"}
[s & body]
(with-open
[s# (-> (java.io.StringReader. ~s)
clojure.lang.LineNumberingPushbackReader.)]
(binding [*in* s#]
~@body)))
(defn pr-str
"pr to a string, returning it"
{:tag String
:added "1.0"
:static true}
[& xs]
(with-out-str
(apply pr xs)))
(defn prn-str
"prn to a string, returning it"
{:tag String
:added "1.0"
:static true}
[& xs]
(with-out-str
(apply prn xs)))
(defn print-str
"print to a string, returning it"
11.1. CORE.CLJ 1255
{:tag String
:added "1.0"
:static true}
[& xs]
(with-out-str
(apply print xs)))
(defn println-str
"println to a string, returning it"
{:tag String
:added "1.0"
:static true}
[& xs]
(with-out-str
(apply println xs)))
(defmacro assert
"Evaluates expr and throws an exception if it does not evaluate to
logical true."
{:added "1.0"}
[x]
(when *assert*
(when-not ~x
(throw
(new AssertionError (str "Assert failed: " (pr-str ~x)))))))
(defn test
"test [v] finds fn at key :test in var metadata and calls it,
presuming failure will throw exception"
{:added "1.0"}
[v]
(let [f (:test (meta v))]
(if f
(do (f) :ok)
:no-test)))
(defn re-pattern
"Returns an instance of java.util.regex.Pattern, for use, e.g. in
re-matcher."
{:tag java.util.regex.Pattern
:added "1.0"
:static true}
[s] (if (instance? java.util.regex.Pattern s)
s
(. java.util.regex.Pattern (compile s))))
(defn re-matcher
"Returns an instance of java.util.regex.Matcher, for use, e.g. in
re-find."
{:tag java.util.regex.Matcher
1256 CHAPTER 11. CLJ/CLOJURE/
:added "1.0"
:static true}
[^java.util.regex.Pattern re s]
(. re (matcher s)))
(defn re-groups
"Returns the groups from the most recent match/find. If there are no
nested groups, returns a string of the entire match. If there are
nested groups, returns a vector of the groups, the first element
being the entire match."
{:added "1.0"
:static true}
[^java.util.regex.Matcher m]
(let [gc (. m (groupCount))]
(if (zero? gc)
(. m (group))
(loop [ret [] c 0]
(if (<= c gc)
(recur (conj ret (. m (group c))) (inc c))
ret)))))
(defn re-seq
"Returns a lazy sequence of successive matches of pattern in string,
using java.util.regex.Matcher.find(), each such match processed with
re-groups."
{:added "1.0"
:static true}
[^java.util.regex.Pattern re s]
(let [m (re-matcher re s)]
((fn step []
(when (. m (find))
(cons (re-groups m) (lazy-seq (step))))))))
(defn re-matches
"Returns the match, if any, of string to pattern, using
java.util.regex.Matcher.matches(). Uses re-groups to return the
groups."
{:added "1.0"
:static true}
[^java.util.regex.Pattern re s]
(let [m (re-matcher re s)]
(when (. m (matches))
(re-groups m))))
(defn re-find
"Returns the next regex match, if any, of string to pattern, using
java.util.regex.Matcher.find(). Uses re-groups to return the
groups."
{:added "1.0"
11.1. CORE.CLJ 1257
:static true}
([^java.util.regex.Matcher m]
(when (. m (find))
(re-groups m)))
([^java.util.regex.Pattern re s]
(let [m (re-matcher re s)]
(re-find m))))
(defn rand
"Returns a random floating point number between 0 (inclusive) and
n (default 1) (exclusive)."
{:added "1.0"
:static true}
([] (. Math (random)))
([n] (* n (rand))))
(defn rand-int
"Returns a random integer between 0 (inclusive) and n (exclusive)."
{:added "1.0"
:static true}
[n] (int (rand n)))
(defmacro defn-
"same as defn, yielding non-public def"
{:added "1.0"}
[name & decls]
(list* defn (with-meta name (assoc (meta name) :private true))
decls))
(defn tree-seq
"Returns a lazy sequence of the nodes in a tree, via a depth-first
walk.branch? must be a fn of one arg that returns true if passed
a node that can have children (but may not). children must be a
fn of one arg that returns a sequence of the children. Will only
be called on nodes for which branch? returns true. Root is the
root node of the tree."
{:added "1.0"
:static true}
[branch? children root]
(let [walk (fn walk [node]
(lazy-seq
(cons node
(when (branch? node)
(mapcat walk (children node))))))]
(walk root)))
(defn file-seq
"A tree seq on java.io.Files"
{:added "1.0"
:static true}
1258 CHAPTER 11. CLJ/CLOJURE/
[dir]
(tree-seq
(fn [^java.io.File f] (. f (isDirectory)))
(fn [^java.io.File d] (seq (. d (listFiles))))
dir))
(defn xml-seq
"A tree seq on the xml elements as per xml/parse"
{:added "1.0"
:static true}
[root]
(tree-seq
(complement string?)
(comp seq :content)
root))
(defn special-symbol?
"Returns true if s names a special form"
{:added "1.0"
:static true}
[s]
(contains? (. clojure.lang.Compiler specials) s))
(defn var?
"Returns true if v is of type clojure.lang.Var"
{:added "1.0"
:static true}
[v] (instance? clojure.lang.Var v))
(defn subs
"Returns the substring of s beginning at start inclusive, and ending
at end (defaults to length of string), exclusive."
{:added "1.0"
:static true}
(^String [^String s start] (. s (substring start)))
(^String [^String s start end] (. s (substring start end))))
(defn max-key
"Returns the x for which (k x), a number, is greatest."
{:added "1.0"
:static true}
([k x] x)
([k x y] (if (> (k x) (k y)) x y))
([k x y & more]
(reduce1 #(max-key k %1 %2) (max-key k x y) more)))
(defn min-key
"Returns the x for which (k x), a number, is least."
{:added "1.0"
:static true}
11.1. CORE.CLJ 1259
([k x] x)
([k x y] (if (< (k x) (k y)) x y))
([k x y & more]
(reduce1 #(min-key k %1 %2) (min-key k x y) more)))
(defn distinct
"Returns a lazy sequence of the elements of coll with
duplicates removed"
{:added "1.0"
:static true}
[coll]
(let [step (fn step [xs seen]
(lazy-seq
((fn [[f :as xs] seen]
(when-let [s (seq xs)]
(if (contains? seen f)
(recur (rest s) seen)
(cons f (step (rest s) (conj seen f))))))
xs seen)))]
(step coll #{})))
(defn replace
"Given a map of replacement pairs and a vector/collection, returns a
vector/seq with any elements = a key in smap replaced with the
corresponding val in smap"
{:added "1.0"
:static true}
[smap coll]
(if (vector? coll)
(reduce1 (fn [v i]
(if-let [e (find smap (nth v i))]
(assoc v i (val e))
v))
coll (range (count coll)))
(map #(if-let [e (find smap %)] (val e) %) coll)))
(defmacro dosync
"Runs the exprs (in an implicit do) in a transaction that encompasses
exprs and any nested calls. Starts a transaction if none is already
running on this thread. Any uncaught exception will abort the
transaction and flow out of dosync. The exprs may be run more than
once, but any effects on Refs will be atomic."
{:added "1.0"}
[& exprs]
(sync nil ~@exprs))
(defmacro with-precision
"Sets the precision and rounding mode to be used for
1260 CHAPTER 11. CLJ/CLOJURE/
BigDecimal operations.
(defn mk-bound-fn
{:private true}
[^clojure.lang.Sorted sc test key]
(fn [e]
(test (.. sc comparator (compare (. sc entryKey e) key)) 0)))
(defn subseq
"sc must be a sorted collection, test(s) one of <, <=, > or
>=. Returns a seq of those entries with keys ek for
which (test (.. sc comparator (compare ek key)) 0) is true"
{:added "1.0"
:static true}
([^clojure.lang.Sorted sc test key]
(let [include (mk-bound-fn sc test key)]
(if (#{> >=} test)
(when-let [[e :as s] (. sc seqFrom key true)]
(if (include e) s (next s)))
(take-while include (. sc seq true)))))
([^clojure.lang.Sorted sc start-test start-key end-test end-key]
(when-let [[e :as s] (. sc seqFrom start-key true)]
(take-while (mk-bound-fn sc end-test end-key)
(if ((mk-bound-fn sc start-test start-key) e) s (next s))))))
(defn rsubseq
"sc must be a sorted collection, test(s) one of <, <=, > or
>=. Returns a reverse seq of those entries with keys ek for
which (test (.. sc comparator (compare ek key)) 0) is true"
{:added "1.0"
:static true}
([^clojure.lang.Sorted sc test key]
(let [include (mk-bound-fn sc test key)]
(if (#{< <=} test)
(when-let [[e :as s] (. sc seqFrom key false)]
(if (include e) s (next s)))
11.1. CORE.CLJ 1261
(defn repeatedly
"Takes a function of no args, presumably with side effects, and
returns an infinite (or length n if supplied) lazy sequence of calls
to it"
{:added "1.0"
:static true}
([f] (lazy-seq (cons (f) (repeatedly f))))
([n f] (take n (repeatedly f))))
(defn add-classpath
"DEPRECATED
(defn hash
"Returns the hash code of its argument"
{:added "1.0"
:static true}
[x] (. clojure.lang.Util (hash x)))
(defn interpose
"Returns a lazy seq of the elements of coll separated by sep"
{:added "1.0"
:static true}
[sep coll] (drop 1 (interleave (repeat sep) coll)))
(defmacro definline
"Experimental - like defmacro, except defines a named function whose
body is the expansion, calls to which may be expanded inline as if
it were a macro. Cannot be used with variadic (&) args."
{:added "1.0"}
[name & decl]
(let [[pre-args [args expr]] (split-with (comp not vector?) decl)]
(do
(defn ~name ~@pre-args ~args
~(apply (eval (list fn args expr)) args))
1262 CHAPTER 11. CLJ/CLOJURE/
(defn empty
"Returns an empty collection of the same category as coll, or nil"
{:added "1.0"
:static true}
[coll]
(when (instance? clojure.lang.IPersistentCollection coll)
(.empty ^clojure.lang.IPersistentCollection coll)))
(defmacro amap
"Maps an expression across an array a, using an index named idx, and
return value named ret, initialized to a clone of a, then setting
each element of ret to the evaluation of expr, returning the new
array ret."
{:added "1.0"}
[a idx ret expr]
(let [a# ~a
~ret (aclone a#)]
(loop [~idx 0]
(if (< ~idx (alength a#))
(do
(aset ~ret ~idx ~expr)
(recur (unchecked-inc ~idx)))
~ret))))
(defmacro areduce
"Reduces an expression across an array a, using an index named idx,
and return value named ret, initialized to init, setting ret to the
evaluation of expr at each step, returning ret."
{:added "1.0"}
[a idx ret init expr]
(let [a# ~a]
(loop [~idx 0 ~ret ~init]
(if (< ~idx (alength a#))
(recur (unchecked-inc ~idx) ~expr)
~ret))))
(defn float-array
"Creates an array of floats"
{:inline (fn [& args] (. clojure.lang.Numbers float_array ~@args))
:inline-arities #{1 2}
:added "1.0"}
([size-or-seq] (. clojure.lang.Numbers float_array size-or-seq))
([size init-val-or-seq]
(. clojure.lang.Numbers float_array size init-val-or-seq)))
(defn boolean-array
"Creates an array of booleans"
11.1. CORE.CLJ 1263
(defn byte-array
"Creates an array of bytes"
{:inline (fn [& args] (. clojure.lang.Numbers byte_array ~@args))
:inline-arities #{1 2}
:added "1.1"}
([size-or-seq] (. clojure.lang.Numbers byte_array size-or-seq))
([size init-val-or-seq]
(. clojure.lang.Numbers byte_array size init-val-or-seq)))
(defn char-array
"Creates an array of chars"
{:inline (fn [& args] (. clojure.lang.Numbers char_array ~@args))
:inline-arities #{1 2}
:added "1.1"}
([size-or-seq] (. clojure.lang.Numbers char_array size-or-seq))
([size init-val-or-seq]
(. clojure.lang.Numbers char_array size init-val-or-seq)))
(defn short-array
"Creates an array of shorts"
{:inline (fn [& args] (. clojure.lang.Numbers short_array ~@args))
:inline-arities #{1 2}
:added "1.1"}
([size-or-seq] (. clojure.lang.Numbers short_array size-or-seq))
([size init-val-or-seq]
(. clojure.lang.Numbers short_array size init-val-or-seq)))
(defn double-array
"Creates an array of doubles"
{:inline (fn [& args] (. clojure.lang.Numbers double_array ~@args))
:inline-arities #{1 2}
:added "1.0"}
([size-or-seq] (. clojure.lang.Numbers double_array size-or-seq))
([size init-val-or-seq]
(. clojure.lang.Numbers double_array size init-val-or-seq)))
(defn object-array
"Creates an array of objects"
{:inline (fn [arg] (. clojure.lang.RT object_array ~arg))
:inline-arities #{1}
:added "1.2"}
([size-or-seq] (. clojure.lang.RT object_array size-or-seq)))
1264 CHAPTER 11. CLJ/CLOJURE/
(defn int-array
"Creates an array of ints"
{:inline (fn [& args] (. clojure.lang.Numbers int_array ~@args))
:inline-arities #{1 2}
:added "1.0"}
([size-or-seq] (. clojure.lang.Numbers int_array size-or-seq))
([size init-val-or-seq]
(. clojure.lang.Numbers int_array size init-val-or-seq)))
(defn long-array
"Creates an array of longs"
{:inline (fn [& args] (. clojure.lang.Numbers long_array ~@args))
:inline-arities #{1 2}
:added "1.0"}
([size-or-seq] (. clojure.lang.Numbers long_array size-or-seq))
([size init-val-or-seq]
(. clojure.lang.Numbers long_array size init-val-or-seq)))
(definline booleans
"Casts to boolean[]"
{:added "1.1"}
[xs] (. clojure.lang.Numbers booleans ~xs))
(definline bytes
"Casts to bytes[]"
{:added "1.1"}
[xs] (. clojure.lang.Numbers bytes ~xs))
(definline chars
"Casts to chars[]"
{:added "1.1"}
[xs] (. clojure.lang.Numbers chars ~xs))
(definline shorts
"Casts to shorts[]"
{:added "1.1"}
[xs] (. clojure.lang.Numbers shorts ~xs))
(definline floats
"Casts to float[]"
{:added "1.0"}
[xs] (. clojure.lang.Numbers floats ~xs))
(definline ints
"Casts to int[]"
{:added "1.0"}
[xs] (. clojure.lang.Numbers ints ~xs))
(definline doubles
"Casts to double[]"
11.1. CORE.CLJ 1265
{:added "1.0"}
[xs] (. clojure.lang.Numbers doubles ~xs))
(definline longs
"Casts to long[]"
{:added "1.0"}
[xs] (. clojure.lang.Numbers longs ~xs))
(defn seque
"Creates a queued seq on another (presumably lazy) seq s. The queued
seq will produce a concrete seq in the background, and can get up to
n items ahead of the consumer. n-or-q can be an integer n buffer
size, or an instance of java.util.concurrent BlockingQueue. Note
that reading from a seque can block if the reader gets ahead of the
producer."
{:added "1.0"
:static true}
([s] (seque 100 s))
([n-or-q s]
(let [^BlockingQueue q (if (instance? BlockingQueue n-or-q)
n-or-q
(LinkedBlockingQueue. (int n-or-q)))
NIL (Object.) ;nil sentinel since LBQ doesnt support nils
agt (agent (seq s))
fill (fn [s]
(try
(loop [[x & xs :as s] s]
(if s
(if (.offer q (if (nil? x) NIL x))
(recur xs)
s)
(.put q q))) ; q itself is eos sentinel
(catch Exception e
(.put q q)
(throw e))))
drain (fn drain []
(lazy-seq
(let [x (.take q)]
(if (identical? x q) ;q itself is eos sentinel
(do @agt nil) ;touch agent just to propagate errors
(do
(send-off agt fill)
(cons
(if (identical? x NIL) nil x)
(drain)))))))]
(send-off agt fill)
(drain))))
1266 CHAPTER 11. CLJ/CLOJURE/
(defn class?
"Returns true if x is an instance of Class"
{:added "1.0"
:static true}
[x] (instance? Class x))
(declare process-annotation)
(defn- add-annotation [^clojure.asm.AnnotationVisitor av name v]
(cond
(vector? v) (let [avec (.visitArray av name)]
(doseq [vval v]
(add-annotation avec "value" vval))
(.visitEnd avec))
(symbol? v) (let [ev (eval v)]
(cond
(instance? java.lang.Enum ev)
(.visitEnum av name (descriptor (class ev)) (str ev))
(class? ev)
(.visit av name (clojure.asm.Type/getType ev))
:else
(throw (IllegalArgumentException.
(str "Unsupported annotation value: " v
" of class " (class ev))))))
(seq? v) (let [[nested nv] v
c (resolve nested)
nav (.visitAnnotation av name (descriptor c))]
(process-annotation nav nv)
(.visitEnd nav))
:else (.visit av name v)))
(defn- add-annotations
11.1. CORE.CLJ 1267
(defn alter-var-root
"Atomically alters the root binding of var v by applying f to its
current value plus any args"
{:added "1.0"
:static true}
[^clojure.lang.Var v f & args] (.alterRoot v f args))
(defn bound?
"Returns true if all of the vars provided as arguments have
any bound value, root or thread-local. Implies that derefing
the provided vars will succeed. Returns true if no vars are provided."
{:added "1.2"
:static true}
[& vars]
(every? #(.isBound ^clojure.lang.Var %) vars))
(defn thread-bound?
"Returns true if all of the vars provided as arguments have
thread-local bindings. Implies that set!ing the provided vars
will succeed. Returns true if no vars are provided."
{:added "1.2"
:static true}
[& vars]
(every? #(.getThreadBinding ^clojure.lang.Var %) vars))
(defn make-hierarchy
"Creates a hierarchy object for use with derive, isa? etc."
{:added "1.0"
:static true}
[] {:parents {} :descendants {} :ancestors {}})
(defn not-empty
"If coll is empty, returns nil, else coll"
{:added "1.0"
:static true}
[coll] (when (seq coll) coll))
(defn bases
"Returns the immediate superclass and direct interfaces of c, if any"
{:added "1.0"
:static true}
[^Class c]
(when c
(let [i (.getInterfaces c)
s (.getSuperclass c)]
(not-empty
(if s (cons s i) i)))))
(defn supers
"Returns the immediate and indirect superclasses and
interfaces of c, if any"
{:added "1.0"
:static true}
[^Class class]
(loop [ret (set (bases class)) cs ret]
(if (seq cs)
(let [c (first cs) bs (bases c)]
(recur (into1 ret bs) (into1 (disj cs c) bs)))
(not-empty ret))))
(defn isa?
"Returns true if (= child parent), or child is directly or
indirectly derived from parent, either via a Java type
inheritance relationship or a relationship established via derive.
h must be a hierarchy obtained from make-hierarchy, if not
supplied defaults to the global hierarchy"
{:added "1.0"}
([child parent] (isa? global-hierarchy child parent))
([h child parent]
(or (= child parent)
(and (class? parent) (class? child)
(. ^Class parent isAssignableFrom child))
(contains? ((:ancestors h) child) parent)
(and (class? child)
(some #(contains? ((:ancestors h) %) parent)
(supers child)))
(and (vector? parent) (vector? child)
(= (count parent) (count child))
(loop [ret true i 0]
(if (or (not ret) (= i (count parent)))
11.1. CORE.CLJ 1269
ret
(recur (isa? h (child i) (parent i)) (inc i))))))))
(defn parents
"Returns the immediate parents of tag, either via a Java type
inheritance relationship or a relationship established via derive. h
must be a hierarchy obtained from make-hierarchy, if not supplied
defaults to the global hierarchy"
{:added "1.0"}
([tag] (parents global-hierarchy tag))
([h tag] (not-empty
(let [tp (get (:parents h) tag)]
(if (class? tag)
(into1 (set (bases tag)) tp)
tp)))))
(defn ancestors
"Returns the immediate and indirect parents of tag, either via a
Java type inheritance relationship or a relationship established
via derive. h must be a hierarchy obtained from make-hierarchy,
if not supplied defaults to the global hierarchy"
{:added "1.0"}
([tag] (ancestors global-hierarchy tag))
([h tag] (not-empty
(let [ta (get (:ancestors h) tag)]
(if (class? tag)
(let [superclasses (set (supers tag))]
(reduce1 into1 superclasses
(cons ta
(map #(get (:ancestors h) %) superclasses))))
ta)))))
(defn descendants
"Returns the immediate and indirect children of tag, through a
relationship established via derive. h must be a hierarchy obtained
from make-hierarchy, if not supplied defaults to the global
hierarchy. Note: does not work on Java type inheritance
relationships."
{:added "1.0"}
([tag] (descendants global-hierarchy tag))
([h tag] (if (class? tag)
(throw (java.lang.UnsupportedOperationException.
"Cant get descendants of classes"))
(not-empty (get (:descendants h) tag)))))
(defn derive
"Establishes a parent/child relationship between parent and
tag. Parent must be a namespace-qualified symbol or keyword and
child can be either a namespace-qualified symbol or keyword or a
class. h must be a hierarchy obtained from make-hierarchy, if not
1270 CHAPTER 11. CLJ/CLOJURE/
(declare flatten)
(defn underive
"Removes a parent/child relationship between parent and
tag. h must be a hierarchy obtained from make-hierarchy, if not
supplied defaults to, and modifies, the global hierarchy."
{:added "1.0"}
([tag parent]
(alter-var-root #global-hierarchy underive tag parent) nil)
([h tag parent]
(let [parentMap (:parents h)
childsParents (if (parentMap tag)
(disj (parentMap tag) parent) #{})
newParents (if (not-empty childsParents)
11.1. CORE.CLJ 1271
(defn distinct?
"Returns true if no two of the arguments are ="
{:tag Boolean
:added "1.0"
:static true}
([x] true)
([x y] (not (= x y)))
([x y & more]
(if (not= x y)
(loop [s #{x y} [x & etc :as xs] more]
(if xs
(if (contains? s x)
false
(recur (conj s x) etc))
true))
false)))
(defn resultset-seq
"Creates and returns a lazy sequence of structmaps corresponding to
the rows in the java.sql.ResultSet rs"
{:added "1.0"}
[^java.sql.ResultSet rs]
(let [rsmeta (. rs (getMetaData))
idxs (range 1 (inc (. rsmeta (getColumnCount))))
keys (map (comp keyword #(.toLowerCase ^String %))
(map (fn [i] (. rsmeta (getColumnLabel i))) idxs))
check-keys
(or (apply distinct? keys)
(throw (Exception.
"ResultSet must have unique column labels")))
row-struct (apply create-struct keys)
row-values
(fn [] (map (fn [^Integer i] (. rs (getObject i))) idxs))
rows
(fn thisfn []
(when (. rs (next))
(cons
(apply struct row-struct (row-values))
1272 CHAPTER 11. CLJ/CLOJURE/
(lazy-seq (thisfn)))))]
(rows)))
(defn iterator-seq
"Returns a seq on a java.util.Iterator. Note that most collections
providing iterators implement Iterable and thus support seq directly."
{:added "1.0"
:static true}
[iter]
(clojure.lang.IteratorSeq/create iter))
(defn enumeration-seq
"Returns a seq on a java.util.Enumeration"
{:added "1.0"
:static true}
[e]
(clojure.lang.EnumerationSeq/create e))
(defn format
"Formats a string using java.lang.String.format,
see java.util.Formatter for format
string syntax"
{:added "1.0"
:static true}
^String [fmt & args]
(String/format fmt (to-array args)))
(defn printf
"Prints formatted output, as per format"
{:added "1.0"
:static true}
[fmt & args]
(print (apply format fmt args)))
(declare gen-class)
(defmacro ns
"Sets *ns* to the namespace named by name (unevaluated), creating it
if needed. references can be zero or more of: (:refer-clojure ...)
(:require ...) (:use ...) (:import ...) (:load ...) (:gen-class)
11.1. CORE.CLJ 1273
(ns foo.bar
(:refer-clojure :exclude [ancestors printf])
(:require (clojure.contrib sql sql.tests))
(:use (my.lib this that))
(:import (java.util Date Timer Random)
(java.sql Connection Statement)))"
{:arglists ([name docstring? attr-map? references*])
:added "1.0"}
[name & references]
(let [process-reference
(fn [[kname & args]]
(~(symbol "clojure.core" (clojure.core/name kname))
~@(map #(list quote %) args)))
docstring (when (string? (first references)) (first references))
references (if docstring (next references) references)
name (if docstring
(vary-meta name assoc :doc docstring)
name)
metadata (when (map? (first references)) (first references))
references (if metadata (next references) references)
name (if metadata
(vary-meta name merge metadata)
name)
gen-class-clause
(first (filter #(= :gen-class (first %)) references))
gen-class-call
(when gen-class-clause
(list* gen-class :name
(.replace (str name) \- \_)
:impl-ns name :main true (next gen-class-clause)))
references (remove #(= :gen-class (first %)) references)
;ns-effect (clojure.core/in-ns name)
]
(do
(clojure.core/in-ns ~name)
(with-loading-context
~@(when gen-class-call (list gen-class-call))
~@(when (and (not= name clojure.core)
(not-any? #(= :refer-clojure (first %)) references))
1274 CHAPTER 11. CLJ/CLOJURE/
((clojure.core/refer ~clojure.core)))
~@(map process-reference references)))))
(defmacro refer-clojure
"Same as (refer clojure.core <filters>)"
{:added "1.0"}
[& filters]
(clojure.core/refer ~clojure.core ~@filters))
(defmacro defonce
"defs name to have the root value of the expr iff the named var
has no root value, else expr is unevaluated"
{:added "1.0"}
[name expr]
(let [v# (def ~name)]
(when-not (.hasRoot v#)
(def ~name ~expr))))
(defonce ^:dynamic
^{:private true
:doc "A ref to a sorted set of symbols representing loaded libs"}
*loaded-libs* (ref (sorted-set)))
(defonce ^:dynamic
^{:private true
:doc "the set of paths currently being loaded by this thread"}
*pending-paths* #{})
(defonce ^:dynamic
^{:private true :doc
"True while a verbose load is pending"}
*loading-verbosely* false)
(defn- throw-if
"Throws an exception with a message if pred is true"
[pred fmt & args]
(when pred
(let [^String message (apply format fmt args)
exception (Exception. message)
raw-trace (.getStackTrace exception)
boring?
#(not= (.getMethodName ^StackTraceElement %) "doInvoke")
trace (into-array (drop 2 (drop-while boring? raw-trace)))]
(.setStackTrace exception trace)
(throw exception))))
(defn- libspec?
"Returns true if x is a libspec"
11.1. CORE.CLJ 1275
[x]
(or (symbol? x)
(and (vector? x)
(or
(nil? (second x))
(keyword? (second x))))))
(defn- prependss
"Prepends a symbol or a seq to coll"
[x coll]
(if (symbol? x)
(cons x coll)
(concat x coll)))
(defn- root-resource
"Returns the root directory path for a lib"
{:tag String}
[lib]
(str \/
(.. (name lib)
(replace \- \_)
(replace \. \/))))
(defn- root-directory
"Returns the root resource path for a lib"
[lib]
(let [d (root-resource lib)]
(subs d 0 (.lastIndexOf d "/"))))
(declare load)
(defn- load-one
"Loads a lib given its name. If need-ns, ensures that the associated
namespace exists after loading. If require, records the load so any
duplicate loads can be skipped."
[lib need-ns require]
(load (root-resource lib))
(throw-if (and need-ns (not (find-ns lib)))
"namespace %s not found after loading %s"
lib (root-resource lib))
(when require
(dosync
(commute *loaded-libs* conj lib))))
(defn- load-all
"Loads a lib given its name and forces a load of any libs it
directly or indirectly loads. If need-ns, ensures that the
associated namespace exists after loading. If require, records
the load so any duplicate loads can be skipped."
[lib need-ns require]
1276 CHAPTER 11. CLJ/CLOJURE/
(dosync
(commute *loaded-libs* #(reduce1 conj %1 %2)
(binding [*loaded-libs* (ref (sorted-set))]
(load-one lib need-ns require)
@*loaded-libs*))))
(defn- load-lib
"Loads a lib with options"
[prefix lib & options]
(throw-if (and prefix (pos? (.indexOf (name lib) (int \.))))
"lib names inside prefix lists must not contain periods")
(let [lib (if prefix (symbol (str prefix \. lib)) lib)
opts (apply hash-map options)
{:keys [as reload reload-all require use verbose]} opts
loaded (contains? @*loaded-libs* lib)
load (cond reload-all
load-all
(or reload (not require) (not loaded))
load-one)
need-ns (or as use)
filter-opts (select-keys opts (:exclude :only :rename))]
(binding [*loading-verbosely* (or *loading-verbosely* verbose)]
(if load
(load lib need-ns require)
(throw-if (and need-ns (not (find-ns lib)))
"namespace %s not found" lib))
(when (and need-ns *loading-verbosely*)
(printf "(clojure.core/in-ns %s)\n" (ns-name *ns*)))
(when as
(when *loading-verbosely*
(printf "(clojure.core/alias %s %s)\n" as lib))
(alias as lib))
(when use
(when *loading-verbosely*
(printf "(clojure.core/refer %s" lib)
(doseq [opt filter-opts]
(printf " %s %s" (key opt) (print-str (val opt))))
(printf ")\n"))
(apply refer lib (mapcat seq filter-opts))))))
(defn- load-libs
"Loads libs, interpreting libspecs, prefix lists, and flags for
forwarding to load-lib"
[& args]
(let [flags (filter keyword? args)
opts (interleave flags (repeat true))
args (filter (complement keyword?) args)]
; check for unsupported options
(let [supported #{:as :reload :reload-all :require :use :verbose}
unsupported (seq (remove supported flags))]
11.1. CORE.CLJ 1277
(throw-if unsupported
(apply str "Unsupported option(s) supplied: "
(interpose \, unsupported))))
; check a load target was specified
(throw-if (not (seq args)) "Nothing specified to load")
(doseq [arg args]
(if (libspec? arg)
(apply load-lib nil (prependss arg opts))
(let [[prefix & args] arg]
(throw-if (nil? prefix) "prefix cannot be nil")
(doseq [arg args]
(apply load-lib prefix (prependss arg opts))))))))
;; Public
(defn require
"Loads libs, skipping any that are already loaded. Each argument is
either a libspec that identifies a lib, a prefix list that identifies
multiple libs whose names share a common prefix, or a flag that
modifies how all the identified libs are loaded. Use :require in
the ns macro in preference to calling this directly.
Libs
require loads a lib by loading its root resource. The root resource
path is derived from the lib name in the following manner:
Consider a lib named by the symbol x.y.z; it has the root directory
<classpath>/x/y/, and its root resource is <classpath>/x/y/z.clj. The
root resource should contain code to create the libs namespace
(usually by using the ns macro) and load any additional lib resources.
Libspecs
Prefix Lists
Its common for Clojure code to depend on several libs whose names
have the same prefix. When specifying libs, prefix lists can be
used to reduce repetition. A prefix list contains the shared prefix
followed by libspecs with the shared prefix removed from the lib
names. After removing the prefix, the names that remain must not
contain any periods.
Flags
A flag is a keyword.
Recognized flags: :reload, :reload-all, :verbose
:reload forces loading of all the identified libs even if they are
already loaded
:reload-all implies :reload and also forces loading of all libs that
the identified libs directly or indirectly load via require or use
:verbose triggers printing information about each load, alias, and
refer
Example:
[& args]
(apply load-libs :require args))
(defn use
"Like require, but also refers to each libs namespace using
clojure.core/refer. Use :use in the ns macro in preference to
calling this directly.
(defn loaded-libs
"Returns a sorted set of symbols naming the currently loaded libs"
{:added "1.0"}
[] @*loaded-libs*)
(defn load
"Loads Clojure code from resources in classpath. A path is
interpreted as classpath-relative if it begins with a slash
11.1. CORE.CLJ 1279
(defn compile
"Compiles the namespace named by the symbol lib into a set of
classfiles. The source for the lib must be in a proper
classpath-relative directory. The output files will go into the
directory specified by *compile-path*, and that directory too must
be in the classpath."
{:added "1.0"}
[lib]
(binding [*compile-files* true]
(load-one lib true true))
lib)
(defn get-in
"Returns the value in a nested associative structure,
where ks is a sequence of ke(ys. Returns nil if the key is not present,
or the not-found value if supplied."
{:added "1.2"
:static true}
([m ks]
(reduce1 get m ks))
([m ks not-found]
(loop [sentinel (Object.)
m m
ks (seq ks)]
(if ks
(let [m (get m (first ks) sentinel)]
(if (identical? sentinel m)
not-found
(recur sentinel m (next ks))))
m))))
1280 CHAPTER 11. CLJ/CLOJURE/
(defn assoc-in
"Associates a value in a nested associative structure, where ks
is a sequence of keys and v is the new value and returns a new
nested structure. If any levels do not exist, hash-maps will be
created."
{:added "1.0"
:static true}
[m [k & ks] v]
(if ks
(assoc m k (assoc-in (get m k) ks v))
(assoc m k v)))
(defn update-in
"Updates a value in a nested associative structure, where ks is a
sequence of keys and f is a function that will take the old value
and any supplied args and return the new value, and returns a new
nested structure. If any levels do not exist, hash-maps will be
created."
{:added "1.0"
:static true}
([m [k & ks] f & args]
(if ks
(assoc m k (apply update-in (get m k) ks f args))
(assoc m k (apply f (get m k) args)))))
(defn empty?
"Returns true if coll has no items - same as (not (seq coll)).
Please use the idiom (seq x) rather than (not (empty? x))"
{:added "1.0"
:static true}
[coll] (not (seq coll)))
(defn coll?
"Returns true if x implements IPersistentCollection"
{:added "1.0"
:static true}
[x] (instance? clojure.lang.IPersistentCollection x))
(defn list?
"Returns true if x implements IPersistentList"
{:added "1.0"
:static true}
[x] (instance? clojure.lang.IPersistentList x))
(defn set?
"Returns true if x implements IPersistentSet"
{:added "1.0"
:static true}
11.1. CORE.CLJ 1281
(defn ifn?
"Returns true if x implements IFn. Note that many data structures
(e.g. sets and maps) implement IFn"
{:added "1.0"
:static true}
[x] (instance? clojure.lang.IFn x))
(defn fn?
"Returns true if x implements Fn, i.e. is an object created via fn."
{:added "1.0"
:static true}
[x] (instance? clojure.lang.Fn x))
(defn associative?
"Returns true if coll implements Associative"
{:added "1.0"
:static true}
[coll] (instance? clojure.lang.Associative coll))
(defn sequential?
"Returns true if coll implements Sequential"
{:added "1.0"
:static true}
[coll] (instance? clojure.lang.Sequential coll))
(defn sorted?
"Returns true if coll implements Sorted"
{:added "1.0"
:static true}
[coll] (instance? clojure.lang.Sorted coll))
(defn counted?
"Returns true if coll implements count in constant time"
{:added "1.0"
:static true}
[coll] (instance? clojure.lang.Counted coll))
(defn reversible?
"Returns true if coll implements Reversible"
{:added "1.0"
:static true}
[coll] (instance? clojure.lang.Reversible coll))
(def ^:dynamic
^{:doc "bound in a repl thread to the most recent value printed"
:added "1.0"}
*1)
1282 CHAPTER 11. CLJ/CLOJURE/
(def ^:dynamic
^{:doc "bound in a repl thread to the second most recent value printed"
:added "1.0"}
*2)
(def ^:dynamic
^{:doc "bound in a repl thread to the third most recent value printed"
:added "1.0"}
*3)
(def ^:dynamic
^{:doc "bound in a repl thread to the most recent exception caught
by the repl"
:added "1.0"}
*e)
(defn trampoline
"trampoline can be used to convert algorithms requiring mutual
recursion without stack consumption. Calls f with supplied args, if
any. If f returns a fn, calls that fn with no arguments, and
continues to repeat, until the return value is not a fn, then
returns that non-fn value. Note that if you want to return a fn as a
final value, you must wrap it in some data structure and unpack it
after trampoline returns."
{:added "1.0"
:static true}
([f]
(let [ret (f)]
(if (fn? ret)
(recur ret)
ret)))
([f & args]
(trampoline #(apply f args))))
(defn intern
"Finds or creates a var named by the symbol name in the namespace
ns (which can be a symbol or a namespace), setting its root binding
to val if supplied. The namespace must exist. The var will adopt any
metadata from the name symbol. Returns the var."
{:added "1.0"
:static true}
([ns ^clojure.lang.Symbol name]
(let [v (clojure.lang.Var/intern (the-ns ns) name)]
(when (meta name) (.setMeta v (meta name)))
v))
([ns name val]
(let [v (clojure.lang.Var/intern (the-ns ns) name val)]
(when (meta name) (.setMeta v (meta name)))
v)))
11.1. CORE.CLJ 1283
(defmacro while
"Repeatedly executes body while test expression is true. Presumes
some side-effect will cause test to become false/nil. Returns nil"
{:added "1.0"}
[test & body]
(loop []
(when ~test
~@body
(recur))))
(defn memoize
"Returns a memoized version of a referentially transparent function.
The memoized version of the function keeps a cache of the mapping
from arguments to results and, when calls with the same arguments
are repeated often, has higher performance at the expense of higher
memory use."
{:added "1.0"
:static true}
[f]
(let [mem (atom {})]
(fn [& args]
(if-let [e (find @mem args)]
(val e)
(let [ret (apply f args)]
(swap! mem assoc args ret)
ret)))))
(defmacro condp
"Takes a binary predicate, an expression, and a set of clauses.
Each clause can take the form of either:
test-expr result-expr
(add-doc-and-meta *file*
"The path of the file being evaluated, as a String.
(add-doc-and-meta *command-line-args*
"A sequence of the supplied command line arguments, or nil if
none were supplied"
{:added "1.0"})
(add-doc-and-meta *warn-on-reflection*
"When set to true, the compiler will emit warnings when reflection is
needed to resolve Java method calls or field accesses.
Defaults to false."
{:added "1.0"})
(add-doc-and-meta *compile-path*
11.1. CORE.CLJ 1285
Defaults to \"classes\""
{:added "1.0"})
(add-doc-and-meta *compile-files*
"Set to true when compiling files, false otherwise."
{:added "1.0"})
(add-doc-and-meta *unchecked-math*
"While bound to true, compilations of +, -, *, inc, dec and the
coercions will be done without overflow checks. Default: false."
{:added "1.3"})
(add-doc-and-meta *ns*
"A clojure.lang.Namespace object representing the current namespace."
{:added "1.0"})
(add-doc-and-meta *in*
"A java.io.Reader object representing standard input for read
operations.
(add-doc-and-meta *out*
"A java.io.Writer object representing standard output for print
operations.
(add-doc-and-meta *err*
"A java.io.Writer object representing standard error for print
operations.
(add-doc-and-meta *flush-on-newline*
"When set to true, output will be flushed whenever a newline is
printed.
Defaults to true."
{:added "1.0"})
(add-doc-and-meta *print-meta*
"If set to logical true, when printing an object, its metadata
1286 CHAPTER 11. CLJ/CLOJURE/
will also be printed in a form that can be read back by the reader.
Defaults to false."
{:added "1.0"})
(add-doc-and-meta *print-dup*
"When set to logical true, objects will be printed in a way that
preserves their type when read in later.
Defaults to false."
{:added "1.0"})
(add-doc-and-meta *print-readably*
"When set to logical false, strings and characters will be printed
with non-alphanumeric characters converted to the appropriate
escape sequences.
Defaults to true"
{:added "1.0"})
(add-doc-and-meta *read-eval*
"When set to logical false, the EvalReader (#=(...)) is disabled
in the read/load in the thread-local binding.
Example:
(binding [*read-eval* false] (read-string \"#=(eval (def x 3))\"))
Defaults to true"
{:added "1.0"})
\getchunk{defn future?}
\getchunk{defn future-done?}
(defmacro letfn
"fnspec ==> (fname [params*] exprs) or (fname ([params*] exprs)+)
(defn- min-hash
"takes a collection of keys and returns [shift mask]"
[keys]
(let [hashes (map hash keys)
cnt (count keys)]
(when-not (apply distinct? hashes)
(throw (IllegalArgumentException. "Hashes must be distinct")))
(or (first
(filter
(fn [[s m]]
(apply distinct? (map #(shift-mask s m %) hashes)))
(for [mask (map #(dec (bit-shift-left 1 %)) (range 1 14))
shift (range 0 31)]
[shift mask])))
(throw (IllegalArgumentException.
"No distinct mapping found")))))
(defmacro case
"Takes an expression, and a set of clauses.
test-constant result-expr
[e & clauses]
(let [ge (with-meta (gensym) {:tag Object})
default (if (odd? (count clauses))
(last clauses)
(throw (IllegalArgumentException.
(str "No matching clause: " ~ge))))
cases (partition 2 clauses)
1288 CHAPTER 11. CLJ/CLOJURE/
(defn into
"Returns a new coll consisting of to-coll with all of the items of
11.1. CORE.CLJ 1289
from-coll conjoined."
{:added "1.0"
:static true}
[to from]
(if (instance? clojure.lang.IEditableCollection to)
(persistent! (reduce conj! (transient to) from))
(reduce conj to from)))
(defn- normalize-slurp-opts
[opts]
(if (string? (first opts))
(do
(println
"WARNING: (slurp f enc) is deprecated, use (slurp f :encoding enc).")
[:encoding (first opts)])
opts))
(defn slurp
"Opens a reader on f and reads all its contents, returning a string.
See clojure.java.io/reader for a complete list of supported arguments."
{:added "1.0"}
([f & opts]
(let [opts (normalize-slurp-opts opts)
sb (StringBuilder.)]
(with-open [#^java.io.Reader r (apply jio/reader f opts)]
(loop [c (.read r)]
(if (neg? c)
(str sb)
(do
(.append sb (char c))
(recur (.read r)))))))))
(defn spit
"Opposite of slurp. Opens f with writer, writes content, then
closes f. Options passed to clojure.java.io/writer."
{:added "1.2"}
[f content & options]
(with-open [#^java.io.Writer w (apply jio/writer f options)]
(.write w (str content))))
\getchunk{defn future-call}
\getchunk{defmacro future}
\getchunk{defn future-cancel}
\getchunk{defn future-cancelled?}
1290 CHAPTER 11. CLJ/CLOJURE/
(defn pmap
"Like map, except f is applied in parallel. Semi-lazy in that the
parallel computation stays ahead of the consumption, but doesnt
realize the entire result unless required. Only useful for
computationally intensive functions where the time of f dominates
the coordination overhead."
{:added "1.0"
:static true}
([f coll]
(let [n (+ 2 (.. Runtime getRuntime availableProcessors))
rets (map #(future (f %)) coll)
step (fn step [[x & xs :as vs] fs]
(lazy-seq
(if-let [s (seq fs)]
(cons (deref x) (step xs (rest s)))
(map deref vs))))]
(step rets (drop n rets))))
([f coll & colls]
(let [step (fn step [cs]
(lazy-seq
(let [ss (map seq cs)]
(when (every? identity ss)
(cons (map first ss) (step (map rest ss)))))))]
(pmap #(apply f %) (step (cons coll colls))))))
(defn pcalls
"Executes the no-arg fns in parallel, returning a lazy sequence of
their values"
{:added "1.0"
:static true}
[& fns] (pmap #(%) fns))
(defmacro pvalues
"Returns a lazy sequence of the values of the exprs, which are
evaluated in parallel"
{:added "1.0"
:static true}
[& exprs]
(pcalls ~@(map #(list fn [] %) exprs)))
(add-doc-and-meta *clojure-version*
"The version info for Clojure core, as a map containing :major :minor
:incremental and :qualifier keys. Feature releases may increment
:minor and/or :major, bugfix releases will increment :incremental.
Possible values of :qualifier include
\"GA\", \"SNAPSHOT\", \"RC-x\" \"BETA-x\""
{:added "1.0"})
(defn
clojure-version
"Returns clojure version as a printable string."
{:added "1.0"}
[]
(str (:major *clojure-version*)
"."
(:minor *clojure-version*)
(when-let [i (:incremental *clojure-version*)]
(str "." i))
(when-let [q (:qualifier *clojure-version*)]
(when (pos? (count q)) (str "-" q)))
(when (:interim *clojure-version*)
"-SNAPSHOT")))
\getchunk{defn promise}
\getchunk{defn deliver}
(defn flatten
"Takes any nested combination of sequential things (lists, vectors,
etc.) and returns their contents as a single, flat sequence.
(flatten nil) returns nil."
{:added "1.2"
:static true}
[x]
(filter (complement sequential?)
(rest (tree-seq sequential? seq x))))
(defn group-by
"Returns a map of the elements of coll keyed by the result of
f on each element. The value at each key will be a vector of the
corresponding elements, in the order they appeared in coll."
{:added "1.2"
1292 CHAPTER 11. CLJ/CLOJURE/
:static true}
[f coll]
(persistent!
(reduce
(fn [ret x]
(let [k (f x)]
(assoc! ret k (conj (get ret k []) x))))
(transient {}) coll)))
(defn partition-by
"Applies f to each value in coll, splitting it each time f returns
a new value. Returns a lazy seq of partitions."
{:added "1.2"
:static true}
[f coll]
(lazy-seq
(when-let [s (seq coll)]
(let [fst (first s)
fv (f fst)
run (cons fst (take-while #(= fv (f %)) (rest s)))]
(cons run (partition-by f (drop (count run) s)))))))
(defn frequencies
"Returns a map from distinct items in coll to the number of times
they appear."
{:added "1.2"
:static true}
[coll]
(persistent!
(reduce (fn [counts x]
(assoc! counts x (inc (get counts x 0))))
(transient {}) coll)))
(defn reductions
"Returns a lazy seq of the intermediate values of the reduction (as
per reduce) of coll by f, starting with init."
{:added "1.2"}
([f coll]
(lazy-seq
(if-let [s (seq coll)]
(reductions f (first s) (rest s))
(list (f)))))
([f init coll]
(cons init
(lazy-seq
(when-let [s (seq coll)]
(reductions f (f init (first s)) (rest s)))))))
(defn rand-nth
"Return a random element of the (sequential) collection. Will have
11.1. CORE.CLJ 1293
(defn partition-all
"Returns a lazy sequence of lists like partition, but may include
partitions with fewer than n items at the end."
{:added "1.2"
:static true}
([n coll]
(partition-all n n coll))
([n step coll]
(lazy-seq
(when-let [s (seq coll)]
(cons (take n s) (partition-all n step (drop step s)))))))
(defn shuffle
"Return a random permutation of coll"
{:added "1.2"
:static true}
[^java.util.Collection coll]
(let [al (java.util.ArrayList. coll)]
(java.util.Collections/shuffle al)
(clojure.lang.RT/vector (.toArray al))))
(defn map-indexed
"Returns a lazy sequence consisting of the result of applying f to 0
and the first item of coll, followed by applying f to 1 and the second
item in coll, etc, until coll is exhausted. Thus function f should
accept 2 arguments, index and item."
{:added "1.2"
:static true}
[f coll]
(letfn
[(mapi [idx coll]
(lazy-seq
(when-let [s (seq coll)]
(if (chunked-seq? s)
(let [c (chunk-first s)
size (int (count c))
b (chunk-buffer size)]
(dotimes [i size]
(chunk-append b (f (+ idx i) (.nth c i))))
(chunk-cons (chunk b) (mapi (+ idx size) (chunk-rest s))))
(cons (f idx (first s)) (mapi (inc idx) (rest s)))))))]
(mapi 0 coll)))
1294 CHAPTER 11. CLJ/CLOJURE/
(defn keep
"Returns a lazy sequence of the non-nil results of (f item). Note,
this means false return values will be included. f must be free of
side-effects."
{:added "1.2"
:static true}
([f coll]
(lazy-seq
(when-let [s (seq coll)]
(if (chunked-seq? s)
(let [c (chunk-first s)
size (count c)
b (chunk-buffer size)]
(dotimes [i size]
(let [x (f (.nth c i))]
(when-not (nil? x)
(chunk-append b x))))
(chunk-cons (chunk b) (keep f (chunk-rest s))))
(let [x (f (first s))]
(if (nil? x)
(keep f (rest s))
(cons x (keep f (rest s))))))))))
(defn keep-indexed
"Returns a lazy sequence of the non-nil results of (f index item).
Note, this means false return values will be included. f must be
free of side-effects."
{:added "1.2"
:static true}
([f coll]
(letfn [(keepi [idx coll]
(lazy-seq
(when-let [s (seq coll)]
(if (chunked-seq? s)
(let [c (chunk-first s)
size (count c)
b (chunk-buffer size)]
(dotimes [i size]
(let [x (f (+ idx i) (.nth c i))]
(when-not (nil? x)
(chunk-append b x))))
(chunk-cons (chunk b)
(keepi (+ idx size) (chunk-rest s))))
(let [x (f idx (first s))]
(if (nil? x)
(keepi (inc idx) (rest s))
(cons x (keepi (inc idx) (rest s)))))))))]
(keepi 0 coll))))
(defn fnil
11.1. CORE.CLJ 1295
(defn with-redefs-fn
"Temporarily redefines Vars during a call to func. Each val of
binding-map will replace the root value of its key which must be
a Var. After func is called with no args, the root values of all
the Vars will be set back to their old values. These temporary
changes will be visible in all threads. Useful for mocking out
functions during testing."
{:added "1.3"}
[binding-map func]
(let [root-bind (fn [m]
(doseq [[a-var a-val] m]
(.bindRoot ^clojure.lang.Var a-var a-val)))
old-vals (zipmap (keys binding-map)
(map deref (keys binding-map)))]
(try
1296 CHAPTER 11. CLJ/CLOJURE/
(root-bind binding-map)
(func)
(finally
(root-bind old-vals)))))
(defmacro with-redefs
"binding => var-symbol temp-value-expr
11.2 protocols.clj
protocols.clj
\getchunk{Clojure Copyright}
(ns clojure.core.protocols)
(defprotocol InternalReduce
"Protocol for concrete seq types that can reduce themselves
faster than first/next recursion. Called by clojure.core/reduce."
(internal-reduce [seq f start]))
(extend-protocol InternalReduce
nil
(internal-reduce
[s f val]
val)
(recur (chunk-next s)
f
(.reduce (chunk-first s) f val))
(internal-reduce s f val))
val))
clojure.lang.StringSeq
(internal-reduce
[str-seq f val]
(let [s (.s str-seq)]
(loop [i (.i str-seq)
val val]
(if (< i (.length s))
(recur (inc i) (f val (.charAt s i)))
val))))
clojure.lang.ArraySeq
(internal-reduce
[a-seq f val]
(let [^objects arr (.array a-seq)]
(loop [i (.index a-seq)
val val]
(if (< i (alength arr))
(recur (inc i) (f val (aget arr i)))
val))))
java.lang.Object
(internal-reduce
[s f val]
(loop [cls (class s)
s s
f f
val val]
(if-let [s (seq s)]
;; roll over to faster implementation if underlying seq changes type
(if (identical? (class s) cls)
(recur cls (next s) f (f val (first s)))
(internal-reduce s f val))
val))))
(def arr-impl
(internal-reduce
[a-seq f val]
(let [arr (.array a-seq)]
(loop [i (.index a-seq)
val val]
(if (< i (alength arr))
(recur (inc i) (f val (aget arr i)))
val)))))
1298 CHAPTER 11. CLJ/CLOJURE/
(defn- emit-array-impls*
[syms]
(apply
concat
(map
(fn [s]
[(symbol (str "clojure.lang.ArraySeq$ArraySeq_" s))
arr-impl])
syms)))
(defmacro emit-array-impls
[& syms]
(extend-protocol InternalReduce
~@(emit-array-impls* syms)))
11.3 coredeftype.clj
coredeftype.clj
\getchunk{Clojure Copyright}
(in-ns clojure.core)
(defn namespace-munge
"Convert a Clojure namespace name to a legal Java package name."
{:added "1.2"}
[ns]
(.replace (str ns) \- \_))
(meta name))]
(let []
(gen-interface :name ~cname :methods ~(vec (map psig sigs)))
(import ~cname))))
(defmacro reify
"reify is a macro with the following structure:
protocol-or-interface-or-Object
(methodName [args+] body)*
1300 CHAPTER 11. CLJ/CLOJURE/
The return type can be indicated by a type hint on the method name,
and arg types can be indicated by a type hint on arg names. If you
leave out all hints, reify will try to match on same name/arity
method in the protocol(s)/interface(s) - this is preferred. If you
supply any hints at all, no inference is done, so all hints (or
default of Object) must be correct, for both arguments and return
type. If a method is overloaded in a protocol/interface, multiple
independent method definitions must be supplied. If overloaded with
same arity in an interface you must specify complete hints to
disambiguate - a missing hint implies Object.
recur works to method heads The method bodies of reify are lexical
closures, and can refer to the surrounding local scope:
(defn hash-combine [x y]
(clojure.lang.Util/hashCombine x (clojure.lang.Util/hash y)))
(defn- imap-cons
[^IPersistentMap this o]
(cond
(instance? java.util.Map$Entry o)
(let [^java.util.Map$Entry pair o]
(.assoc this (.getKey pair) (.getValue pair)))
11.3. COREDEFTYPE.CLJ 1301
(instance? clojure.lang.IPersistentVector o)
(let [^clojure.lang.IPersistentVector vec o]
(.assoc this (.nth vec 0) (.nth vec 1)))
:else (loop [this this
o o]
(if (seq o)
(let [^java.util.Map$Entry pair (first o)]
(recur (.assoc this (.getKey pair) (.getValue pair))
(rest o)))
this))))
(defn- emit-defrecord
"Do not use this directly - use defrecord"
{:added "1.2"}
[tagname name fields interfaces methods]
(let [tag (keyword (str *ns*) (str tagname))
classname
(with-meta (symbol (str (namespace-munge *ns*) "." name))
(meta name))
interfaces (vec interfaces)
interface-set (set (map resolve interfaces))
methodname-set (set (map first methods))
hinted-fields fields
fields (vec (map #(with-meta % nil) fields))
base-fields fields
fields (conj fields __meta __extmap)]
(when (some #{:volatile-mutable :unsynchronized-mutable}
(mapcat (comp keys meta) hinted-fields))
(throw (IllegalArgumentException.
(str ":volatile-mutable or :unsynchronized-mutable not "
"supported for record fields"))))
(let [gs (gensym)]
(letfn
[(eqhash [[i m]]
[i
(conj m
(hashCode [this#]
(clojure.lang.APersistentMap/mapHash this#))
(equals [this# ~gs]
(clojure.lang.APersistentMap/mapEquals this# ~gs)))])
(iobj [[i m]]
[(conj i clojure.lang.IObj)
(conj m (meta [this#] ~__meta)
(withMeta [this# ~gs]
(new ~tagname ~@(replace {__meta gs} fields))))])
(ilookup [[i m]]
[(conj i clojure.lang.ILookup clojure.lang.IKeywordLookup)
(conj m (valAt [this# k#] (.valAt this# k# nil))
(valAt [this# k# else#]
(case k# ~@(mapcat (fn [fld] [(keyword fld) fld])
1302 CHAPTER 11. CLJ/CLOJURE/
base-fields)
(get ~__extmap k# else#)))
(getLookupThunk [this# k#]
(let [~gclass (class this#)]
(case k#
~@(let [hinted-target
(with-meta gtarget {:tag tagname})]
(mapcat
(fn [fld]
[(keyword fld)
(reify clojure.lang.ILookupThunk
(get [~thunk ~gtarget]
(if
(identical?
(class ~gtarget) ~gclass)
(. ~hinted-target
~(keyword fld))
~thunk)))])
base-fields))
nil))))])
(imap [[i m]]
[(conj i clojure.lang.IPersistentMap)
(conj m
(count [this#]
(+ ~(count base-fields) (count ~__extmap)))
(empty [this#]
(throw (UnsupportedOperationException.
(str "Cant create empty: " ~(str classname)))))
(cons [this# e#] ((var imap-cons) this# e#))
(equiv [this# ~gs]
(boolean
(or (identical? this# ~gs)
(when (identical? (class this#) (class ~gs))
(let [~gs ~(with-meta gs {:tag tagname})]
(and
~@(map
(fn [fld]
(= ~fld (. ~gs ~fld)))
base-fields)
(= ~__extmap
(. ~gs ~__extmap))))))))
(containsKey [this# k#]
(not (identical? this# (.valAt this# k# this#))))
(entryAt [this# k#]
(let [v# (.valAt this# k# this#)]
(when-not (identical? this# v#)
(clojure.lang.MapEntry. k# v#))))
(seq [this#]
(seq
(concat
11.3. COREDEFTYPE.CLJ 1303
[~@(map
#(list new clojure.lang.MapEntry
(keyword %) %) base-fields)]
~__extmap)))
(assoc [this# k# ~gs]
(condp identical? k#
~@(mapcat
(fn [fld]
[(keyword fld)
(list* new tagname
(replace {fld gs} fields))])
base-fields)
(new ~tagname
~@(remove #{__extmap} fields)
(assoc ~__extmap k# ~gs))))
(without [this# k#]
(if
(contains? #{~@(map keyword base-fields)} k#)
(dissoc (with-meta (into {} this#) ~__meta) k#)
(new ~tagname ~@(remove #{__extmap} fields)
(not-empty (dissoc ~__extmap k#))))))])
(ijavamap [[i m]]
[(conj i java.util.Map java.io.Serializable)
(conj m
(size [this#] (.count this#))
(isEmpty [this#] (= 0 (.count this#)))
(containsValue [this# v#]
(boolean (some #{v#} (vals this#))))
(get [this# k#] (.valAt this# k#))
(put [this# k# v#]
(throw (UnsupportedOperationException.)))
(remove [this# k#]
(throw (UnsupportedOperationException.)))
(putAll [this# m#]
(throw (UnsupportedOperationException.)))
(clear [this#]
(throw (UnsupportedOperationException.)))
(keySet [this#] (set (keys this#)))
(values [this#] (vals this#))
(entrySet [this#] (set this#)))])
]
(let [[i m]
(-> [interfaces methods] eqhash iobj ilookup imap ijavamap)]
(deftype* ~tagname ~classname
~(conj hinted-fields __meta __extmap)
:implements ~(vec i)
~@m))))))
(defmacro defrecord
"Alpha - subject to change
1304 CHAPTER 11. CLJ/CLOJURE/
protocol-or-interface-or-Object
(methodName [args*] body)*
The argument and return types can be hinted on the arg and
methodname symbols. If not supplied, they will be inferred, so type
hints should be reserved for disambiguation.
In the method bodies, the (unqualified) name can be used to name the
class (for calls to new, instance? etc).
When AOT compiling, generates compiled bytecode for a class with the
given name (a symbol), prepends the current ns as the package, and
writes the .class file to the *compile-path* directory.
(defn- emit-deftype*
"Do not use this directly - use deftype"
[tagname name fields interfaces methods]
(let [classname
(with-meta (symbol (str (namespace-munge *ns*) "." name))
(meta name))]
(deftype* ~tagname ~classname ~fields
:implements ~interfaces
~@methods)))
(defmacro deftype
"Alpha - subject to change
protocol-or-interface-or-Object
(methodName [args*] body)*
The class will have the (by default, immutable) fields named by
fields, which can have type hints. Protocols/interfaces and methods
are optional. The only methods that can be supplied are those
declared in the protocols/interfaces. Note that method bodies are
not closures, the local environment includes only the named fields,
and those fields can be accessed directy. Fields can be qualified
with the metadata :volatile-mutable true or :unsynchronized-mutable
true, at which point (set! afield aval) will be supported in method
bodies. Note well that mutable fields are extremely difficult to use
correctly, and are present only to facilitate the building of higher
level constructs, such as Clojures reference types, in Clojure
itself. They are for experts only - if the semantics and
implications of :volatile-mutable or :unsynchronized-mutable are not
immediately apparent to you, you should not be using them.
The argument and return types can be hinted on the arg and
methodname symbols. If not supplied, they will be inferred, so type
hints should be reserved for disambiguation.
In the method bodies, the (unqualified) name can be used to name the
class (for calls to new, instance? etc).
When AOT compiling, generates compiled bytecode for a class with the
11.3. COREDEFTYPE.CLJ 1307
(defn- pref
([] nil)
([a] a)
([^Class a ^Class b]
(if (.isAssignableFrom a b) b a)))
1308 CHAPTER 11. CLJ/CLOJURE/
(defn- protocol?
[maybe-p]
(boolean (:on-interface maybe-p)))
(defn extends?
"Returns true if atype extends protocol"
{:added "1.2"}
[protocol atype]
(boolean (or (implements? protocol atype)
(get (:impls protocol) atype))))
(defn extenders
"Returns a collection of the types explicitly extending protocol"
{:added "1.2"}
[protocol]
(keys (:impls protocol)))
(defn satisfies?
"Returns true if x satisfies the protocol"
{:added "1.2"}
[protocol x]
(boolean (find-protocol-impl protocol x)))
(defn -cache-protocol-fn
[^clojure.lang.AFunction pf x ^Class c ^clojure.lang.IFn interf]
(let [cache (.__methodImplCache pf)
f (if (.isInstance c x)
interf
11.3. COREDEFTYPE.CLJ 1309
(:arglists sig))))
(vals sigs))]
(do
(defonce ~name {})
(gen-interface :name ~iname :methods ~meths)
(alter-meta! (var ~name) assoc :doc ~(:doc opts))
(#assert-same-protocol (var ~name) ~(map :name (vals sigs)))
(alter-var-root (var ~name) merge
(assoc ~opts
:sigs ~sigs
:var (var ~name)
:method-map
~(and (:on opts)
(apply hash-map
(mapcat
(fn [s]
[(keyword (:name s))
(keyword (or (:on s) (:name s)))])
(vals sigs))))
:method-builders
~(apply hash-map
(mapcat
(fn [s]
[(intern *ns*
(with-meta ~(:name s)
(merge ~s {:protocol (var ~name)})))
(emit-method-builder
(:on-interface opts)
(:name s)
(:on s)
(:arglists s))])
(vals sigs)))))
(-reset-methods ~name)
~name)))
(defmacro defprotocol
"A protocol is a named set of named methods and their signatures:
(defprotocol AProtocolName
;method signatures
(bar [this a b] \"bar docs\")
(baz [this a] [this a b] [this a b c] \"baz docs\"))
Note that you should not use this interface with deftype or
reify, as they support the protocol directly:
(defprotocol P
(foo [this])
(bar-me [this] [this y]))
(deftype Foo [a b c]
P
(foo [this] a)
(bar-me [this] b)
(bar-me [this y] (+ c y)))
(foo
(let [x 42]
(reify P
(foo [this] 17)
(bar-me [this] x)
(bar-me [this y] x))))
=> 17"
{:added "1.2"}
[name & opts+sigs]
(emit-protocol name opts+sigs))
(defn extend
"Implementations of protocol methods can be provided using
the extend construct:
(extend AType
AProtocol
{:foo an-existing-fn
:bar (fn [a b] ...)
:baz (fn ([a]...) ([a b] ...)...)}
BProtocol
{...}
11.3. COREDEFTYPE.CLJ 1313
...)
extend takes a type/class (or interface, see below), and one or more
protocol + method map pairs. It will extend the polymorphism of the
protocols methods to call the supplied methods when an AType is
provided as the first argument.
Note that multiple independent extend clauses can exist for the same
type, not all protocols need be defined in a single extend call.
See also:
extends?, satisfies?, extenders"
{:added "1.2"}
[atype & proto+mmaps]
(doseq [[proto mmap] (partition 2 proto+mmaps)]
(when-not (protocol? proto)
(throw (IllegalArgumentException.
(str proto " is not a protocol"))))
(when (implements? proto atype)
(throw (IllegalArgumentException.
(str atype " already directly implements "
(:on-interface proto) " for protocol:"
(:var proto)))))
(-reset-methods
(alter-var-root (:var proto) assoc-in [:impls atype] mmap))))
(apply vector
(vary-meta target assoc :tag c) args)
body))
specs)))]
[p (zipmap (map #(-> % first keyword) fs)
(map #(cons fn (hint (drop 1 %))) fs))]))
(defmacro extend-type
"A macro that expands into an extend call. Useful when you are
supplying the definitions explicitly inline, extend-type
automatically creates the maps required by extend. Propagates the
class as a type hint on the first argument of all fns.
(extend-type MyType
Countable
(cnt [c] ...)
Foo
(bar [x y] ...)
(baz ([x] ...) ([x y & zs] ...)))
expands into:
(extend MyType
Countable
{:cnt (fn [c] ...)}
Foo
{:baz (fn ([x] ...) ([x y & zs] ...))
:bar (fn [x y] ...)})"
{:added "1.2"}
[t & specs]
(emit-extend-type t specs))
(defmacro extend-protocol
"Useful when you want to provide several implementations of the same
protocol all at once. Takes a single protocol and the implementation
of that protocol for one or more types. Expands into calls to
extend-type:
11.4. COREPRINT.CLJ 1315
(extend-protocol Protocol
AType
(foo [x] ...)
(bar [x y] ...)
BType
(foo [x] ...)
(bar [x y] ...)
AClass
(foo [x] ...)
(bar [x y] ...)
nil
(foo [x] ...)
(bar [x y] ...))
expands into:
(do
(clojure.core/extend-type AType Protocol
(foo [x] ...)
(bar [x y] ...))
(clojure.core/extend-type BType Protocol
(foo [x] ...)
(bar [x y] ...))
(clojure.core/extend-type AClass Protocol
(foo [x] ...)
(bar [x y] ...))
(clojure.core/extend-type nil Protocol
(foo [x] ...)
(bar [x y] ...)))"
{:added "1.2"}
[p & specs]
(emit-extend-protocol p specs))
11.4 coreprint.clj
coreprint.clj
\getchunk{Clojure Copyright}
(in-ns clojure.core)
(def ^:dynamic
^{:doc "*print-length* controls how many items of each collection
the printer will print. If it is bound to logical false, there is
no limit. Otherwise, it must be bound to an integer indicating the
maximum number of items of each collection to print. If a collection
contains more items, the printer will print items up to the limit
followed by ... to represent the remaining items. The root binding
is nil indicating no limit."
:added "1.0"}
*print-length* nil)
(def ^:dynamic
^{:doc "*print-level* controls how many levels deep the printer will
print nested objects. If it is bound to logical false, there is no
limit. Otherwise, it must be bound to an integer indicating the
maximum level to print. Each argument to print is at level 0; if
an argument is a collection, its items are at level 1; and so on.
If an object is a collection and is at a level greater than or equal
to the value bound to *print-level*, the printer prints # to
represent it. The root binding is nil indicating no limit."
:added "1.0"}
*print-level* nil)
(recur xs)))))
(.write w end)))))
(prefer-method print-dup
clojure.lang.IPersistentCollection clojure.lang.Fn)
(prefer-method print-dup java.util.Map clojure.lang.Fn)
(prefer-method print-dup java.util.Collection clojure.lang.Fn)
(prefer-method print-dup
clojure.lang.IPersistentCollection java.util.Collection)
(prefer-method print-dup
clojure.lang.IPersistentCollection java.util.Map)
(defmethod print-dup
clojure.lang.LazilyPersistentVector [o w] (print-method o w))
(def primitives-classnames
{Float/TYPE "Float/TYPE"
Integer/TYPE "Integer/TYPE"
Long/TYPE "Long/TYPE"
Boolean/TYPE "Boolean/TYPE"
Character/TYPE "Character/TYPE"
Double/TYPE "Double/TYPE"
Byte/TYPE "Byte/TYPE"
Short/TYPE "Short/TYPE"})
(.append w \\)
(.append w c2)
(if qmode
(recur r2 (not= c2 \E))
(recur r2 (= c2 \Q))))
(= c \") (do
(if qmode
(.write w "\\E\\\"\\Q")
(.write w "\\\""))
(recur r qmode))
:else (do
(.append w c)
(recur r qmode)))))
(.append w \"))
(defmethod print-dup
java.util.regex.Pattern [p ^Writer w] (print-method p w))
(defmethod print-dup
clojure.lang.Namespace [^clojure.lang.Namespace n ^Writer w]
(.write w "#=(find-ns ")
(print-dup (.name n) w)
(.write w ")"))
-
11.5. COREPROXY.CLJ 1323
11.5 coreproxy.clj
coreproxy.clj
\getchunk{Clojure Copyright}
(in-ns clojure.core)
(import
(clojure.asm ClassWriter ClassVisitor Opcodes Type)
(java.lang.reflect Modifier Constructor)
(clojure.asm.commons Method GeneratorAdapter)
(clojure.lang IProxy Reflector
DynamicClassLoader IPersistentMap PersistentHashMap RT))
(defn proxy-name
{:tag String}
[^Class super interfaces]
(let [inames
(into1 (sorted-set) (map #(.getName ^Class %) interfaces))]
(apply str (.replace (str *ns*) \- \_) ".proxy"
(interleave (repeat "$")
(concat
[(.getName super)]
(map #(subs % (inc (.lastIndexOf ^String % "."))) inames)
[(Integer/toHexString (hash inames))])))))
(else-gen gen m)
(. gen (returnValue))
(. gen (endMethod)))))
;add IProxy methods
(let [m (. Method
(getMethod
"void __initClojureFnMappings(clojure.lang.IPersistentMap)"))
gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
(. gen (visitCode))
(. gen (loadThis))
(. gen (loadArgs))
(. gen (putField ctype fmap imap-type))
(. gen (returnValue))
(. gen (endMethod)))
(let [m (. Method
(getMethod
"void __updateClojureFnMappings(clojure.lang.IPersistentMap)"))
gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
(. gen (visitCode))
(. gen (loadThis))
(. gen (dup))
(. gen (getField ctype fmap imap-type))
(.checkCast gen (totype clojure.lang.IPersistentCollection))
(. gen (loadArgs))
(. gen (invokeInterface (totype clojure.lang.IPersistentCollection)
(. Method
(getMethod
"clojure.lang.IPersistentCollection cons(Object)"))))
(. gen (checkCast imap-type))
(. gen (putField ctype fmap imap-type))
(. gen (returnValue))
11.5. COREPROXY.CLJ 1327
(. gen (endMethod)))
(let [m (. Method
(getMethod
"clojure.lang.IPersistentMap __getClojureFnMappings()"))
gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
(. gen (visitCode))
(. gen (loadThis))
(. gen (getField ctype fmap imap-type))
(. gen (returnValue))
(. gen (endMethod)))
(defn get-proxy-class
"Takes an optional single class followed by zero or more
interfaces. If not supplied class defaults to Object. Creates an
returns an instance of a proxy class derived from the supplied
classes. The resulting value is cached and used for any subsequent
requests for the same class set. Returns a Class object."
{:added "1.0"}
[& bases]
(let [[super interfaces] (get-super-and-interfaces bases)
pname (proxy-name super interfaces)]
(or (RT/loadClassForName pname)
(let [[cname bytecode] (generate-proxy super interfaces)]
11.5. COREPROXY.CLJ 1329
(. ^DynamicClassLoader
(deref clojure.lang.Compiler/LOADER)
(defineClass pname bytecode [super interfaces]))))))
(defn construct-proxy
"Takes a proxy class and any arguments for its superclass ctor and
creates and returns an instance of the proxy."
{:added "1.0"}
[c & ctor-args]
(. Reflector (invokeConstructor c (to-array ctor-args))))
(defn init-proxy
"Takes a proxy instance and a map of strings (which must
correspond to methods of the proxy superclass/superinterfaces) to
fns (which must take arguments matching the corresponding method,
plus an additional (explicit) first arg corresponding to this, and
sets the proxys fn map. Returns the proxy."
{:added "1.0"}
[^IProxy proxy mappings]
(. proxy (__initClojureFnMappings mappings))
proxy)
(defn update-proxy
"Takes a proxy instance and a map of strings (which must
correspond to methods of the proxy superclass/superinterfaces) to
fns (which must take arguments matching the corresponding method,
plus an additional (explicit) first arg corresponding to this, and
updates (via assoc) the proxys fn map. nil can be passed instead of
a fn, in which case the corresponding method will revert to the
default behavior. Note that this function can be used to update the
behavior of an existing instance without changing its identity.
Returns the proxy."
{:added "1.0"}
[^IProxy proxy mappings]
(. proxy (__updateClojureFnMappings mappings))
proxy)
(defn proxy-mappings
"Takes a proxy instance and returns the proxys fn map."
{:added "1.0"}
[^IProxy proxy]
(. proxy (__getClojureFnMappings)))
(defmacro proxy
"class-and-interfaces - a vector of class names
(defmacro proxy-super
"Use to call a superclass method in the body of a proxy method.
Note, expansion captures this"
{:added "1.0"}
[meth & args]
(proxy-call-with-super
(fn [] (. ~this ~meth ~@args)) ~this ~(name meth)))
(defn bean
"Takes a Java object and returns a read-only implementation of the
map abstraction based upon its JavaBean properties."
{:added "1.0"}
[^Object x]
(let [c (. x (getClass))
pmap
(reduce1 (fn [m ^java.beans.PropertyDescriptor pd]
(let [name (. pd (getName))
method (. pd (getReadMethod))]
(if (and method
(zero? (alength (. method (getParameterTypes)))))
(assoc m (keyword name)
(fn []
(clojure.lang.Reflector/prepRet
(.getPropertyType pd) (. method (invoke x nil)))))
m)))
{}
(seq (.. java.beans.Introspector
(getBeanInfo c)
(getPropertyDescriptors))))
11.6 data.clj
data.clj
\getchunk{Clojure Copyright}
(ns
^{:author "Stuart Halloway",
:doc "Non-core data functions."}
clojure.data
(:require [clojure.set :as set]))
(defn- atom-diff
"Internal helper for diff."
[a b]
(if (= a b) [nil nil a] [a b nil]))
(declare diff)
11.6. DATA.CLJ 1333
(extend nil
Diff
{:diff-similar atom-diff})
(extend Object
Diff
{:diff-similar atom-diff}
EqualityPartition
{:equality-partition
(fn [x] (if (.. x getClass isArray) :sequential :atom))})
(defn- diff-associative
"Diff associative things a and b, comparing only keys in ks."
[a b ks]
(reduce
(fn [diff1 diff2]
(map merge diff1 diff2))
[nil nil nil]
(map
(fn [k] (map #(when % {k %}) (diff (get a k) (get b k))))
ks)))
(extend-protocol EqualityPartition
nil
(equality-partition [x] :atom)
java.util.Set
(equality-partition [x] :set)
java.util.List
(equality-partition [x] :sequential)
java.util.Map
(equality-partition [x] :map))
(extend-protocol Diff
java.util.Set
(diff-similar [a b]
[(not-empty (set/difference a b))
1334 CHAPTER 11. CLJ/CLOJURE/
java.util.List
(diff-similar [a b]
(vec (map vectorize (diff-associative
(if (vector? a) a (vec a))
(if (vector? b) b (vec b))
(range (max (count a) (count b)))))))
java.util.Map
(diff-similar [a b]
(diff-associative a b (set/union (keys a) (keys b)))))
(defn diff
"Recursively compares a and b, returning a tuple of
[things-only-in-a things-only-in-b things-in-both].
Comparison rules:
11.7 genclass.clj
genclass.clj
\getchunk{Clojure Copyright}
(in-ns clojure.core)
(loop [c c]
(if (= c Object)
(throw (new Exception
(str "field, " f ", not defined in class,
" start-class ", or its ancestors")))
(let [dflds (.getDeclaredFields c)
rfld (first
(filter
#(= f (.getName ^java.lang.reflect.Field %))
dflds))]
(or rfld (recur (.getSuperclass c))))))))
(defn- validate-generate-class-options
[{:keys [methods]}]
(let [[mname]
(remove valid-java-method-name
(map (comp str first) methods))]
(when mname
(throw (IllegalArgumentException.
(str "Not a valid method name: " mname))))))
11.7. GENCLASS.CLJ 1337
(else-gen gen m)
; class annotations
(add-annotations cv name-meta)
"clojure.lang.Var internPrivate(String,String)"))))
(. gen putStatic ctype (var-name v) var-type))
(when load-impl-ns
(. gen push "clojure.core")
(. gen push "load")
(. gen
(invokeStatic rt-type
(. Method
(getMethod "clojure.lang.Var var(String,String)"))))
(. gen push (str "/" impl-cname))
(. gen
(invokeInterface ifn-type
(new Method "invoke" obj-type (to-types [Object]))))
; (. gen push (str (.replace impl-pkg-name \- \_) "__init"))
; (. gen
; (invokeStatic class-type
; (. Method (getMethod "Class forName(String)"))))
(. gen pop))
(. gen (returnValue))
(. gen (endMethod)))
;ctors
(doseq [[pclasses super-pclasses] ctor-sig-map]
(let [pclasses (map the-class pclasses)
super-pclasses (map the-class super-pclasses)
ptypes (to-types pclasses)
super-ptypes (to-types super-pclasses)
m (new Method "<init>" (. Type VOID_TYPE) ptypes)
super-m
(new Method "<init>" (. Type VOID_TYPE) super-ptypes)
gen
(new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)
no-init-label (. gen newLabel)
end-label (. gen newLabel)
no-post-init-label (. gen newLabel)
end-post-init-label (. gen newLabel)
nth-method (. Method (getMethod "Object nth(Object,int)"))
local (. gen newLocal obj-type)]
(. gen (visitCode))
(if init
(do
(emit-get-var gen init-name)
(. gen dup)
(. gen ifNull no-init-label)
(.checkCast gen ifn-type)
;box init args
(dotimes [i (count pclasses)]
1342 CHAPTER 11. CLJ/CLOJURE/
(. gen (loadThis))
(. gen dupX1)
(dotimes [i (count super-pclasses)]
(. gen loadLocal local)
(. gen push (int i))
(. gen (invokeStatic rt-type nth-method))
(. clojure.lang.Compiler$HostExpr
(emitUnboxArg nil gen (nth super-pclasses i))))
(. gen (invokeConstructor super-type super-m))
(if state
(do
(. gen push (int 1))
(. gen (invokeStatic rt-type nth-method))
(. gen (putField ctype state-name obj-type)))
(. gen pop))
(when post-init
(emit-get-var gen post-init-name)
(. gen dup)
(. gen ifNull no-post-init-label)
(.checkCast gen ifn-type)
11.7. GENCLASS.CLJ 1343
(. gen (loadThis))
;box init args
(dotimes [i (count pclasses)]
(. gen (loadArg i))
(. clojure.lang.Compiler$HostExpr
(emitBoxReturn nil gen (nth pclasses i))))
;call init fn
(. gen (invokeInterface ifn-type
(new Method "invoke" obj-type
(arg-types (inc (count ptypes))))))
(. gen pop)
(. gen goTo end-post-init-label)
;no init found
(. gen mark no-post-init-label)
(. gen (throwException ex-type
(str impl-pkg-name "/" prefix post-init-name
" not defined")))
(. gen mark end-post-init-label))
(. gen (returnValue))
(. gen (endMethod))
;factory
(when factory
(let [fm (new Method factory-name ctype ptypes)
gen (new GeneratorAdapter
(+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_STATIC))
fm nil nil cv)]
(. gen (visitCode))
(. gen newInstance ctype)
(. gen dup)
(. gen (loadArgs))
(. gen (invokeConstructor ctype m))
(. gen (returnValue))
(. gen (endMethod))))))
(. super-type (getInternalName))
(. m (getName))
(. m (getDescriptor)))))))
;add methods matching interfaces, if no fn -> throw
(reduce1 (fn [mm ^java.lang.reflect.Method meth]
(if (contains? mm (method-sig meth))
mm
(do
(emit-forwarding-method
(.getName meth)
(.getParameterTypes meth)
(.getReturnType meth)
false
emit-unsupported)
(assoc mm (method-sig meth) meth))))
mm (mapcat #(.getMethods ^Class %) interfaces))
;extra methods
(doseq [[mname pclasses rclass :as msig] methods]
(emit-forwarding-method mname pclasses rclass
(:static (meta msig))
emit-unsupported))
;expose specified overridden superclass methods
(doseq [[local-mname ^java.lang.reflect.Method m]
(reduce1
(fn [ms [[name _ _] m]]
(if (contains? exposes-methods (symbol name))
(conj ms [((symbol name) exposes-methods) m])
ms)) [] (seq mm))]
(let [ptypes (to-types (.getParameterTypes m))
rtype (totype (.getReturnType m))
exposer-m (new Method (str local-mname) rtype ptypes)
target-m (new Method (.getName m) rtype ptypes)
gen (new GeneratorAdapter
(. Opcodes ACC_PUBLIC) exposer-m nil nil cv)]
(. gen (loadThis))
(. gen (loadArgs))
(. gen (visitMethodInsn (. Opcodes INVOKESPECIAL)
(. super-type (getInternalName))
(. target-m (getName))
(. target-m (getDescriptor))))
(. gen (returnValue))
(. gen (endMethod)))))
;main
(when main
(let [m (. Method getMethod "void main (String[])")
gen (new GeneratorAdapter
(+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_STATIC))
m nil nil cv)
no-main-label (. gen newLabel)
end-label (. gen newLabel)]
11.7. GENCLASS.CLJ 1345
(. gen (visitCode))
(. gen (returnValue))
(. gen (endMethod))))))
;finish class def
(. cv (visitEnd))
[cname (. cv (toByteArray))]))
(defmacro gen-class
"When compiling, generates compiled bytecode for a class with the
given package-qualified :name (which, as all names in these
parameters, can be a string or symbol), and writes the .class file
to the *compile-path* directory. When not compiling, does
nothing. The gen-class construct contains no implementation, as the
implementation will be dynamically sought by the generated class in
functions in an implementing Clojure namespace. Given a generated
class org.mydomain.MyClass with a method named mymethod, gen-class
will generate an implementation that looks for a function named by
(str prefix mymethod) (default prefix: \"-\") in a
Clojure namespace specified by :impl-ns
(defaults to the current namespace). All inherited methods,
generated methods, and init and main functions (see :methods, :init,
and :main below) will be found similarly prefixed. By default, the
static initializer for the generated class will attempt to load the
Clojure support code for the class as a resource from the classpath,
e.g. in the example case, org/mydomain/MyClass__init.class. This
behavior can be controlled by :load-impl-ns
:name aname
:extends aclass
:init name
:post-init name
:main boolean
:factory name
:state name
If supplied, a public final instance field with the given name will be
created. You must supply an :init function in order to provide a
value for the state. Note that, though final, the state can be a ref
1348 CHAPTER 11. CLJ/CLOJURE/
:prefix string
Default: \"-\" Methods called e.g. Foo will be looked up in vars called
prefixFoo in the implementing ns.
:impl-ns name
:load-impl-ns boolean
Default: true. Causes the static initializer for the generated class
to reference the load code for the implementing namespace. Should be
true when implementing-ns is the default, false if you intend to
load the code via some other method."
{:added "1.0"}
[& options]
(when *compile-files*
(let [options-map (into1 {} (map vec (partition 2 options)))
[cname bytecode] (generate-class options-map)]
(clojure.lang.Compiler/writeClassFile cname bytecode))))
(defn- generate-interface
[{:keys [name extends methods]}]
(let [iname (.replace (str name) "." "/")
cv (ClassWriter. ClassWriter/COMPUTE_MAXS)]
(. cv visit Opcodes/V1_5 (+ Opcodes/ACC_PUBLIC
Opcodes/ACC_ABSTRACT
Opcodes/ACC_INTERFACE)
iname nil "java/lang/Object"
(when (seq extends)
(into-array (map #(.getInternalName (asm-type %)) extends))))
(add-annotations cv (meta name))
(doseq [[mname pclasses rclass pmetas] methods]
(let [mv (. cv visitMethod
(+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT)
(str mname)
(Type/getMethodDescriptor (asm-type rclass)
(if pclasses
(into-array Type (map asm-type pclasses))
(make-array Type 0)))
nil nil)]
(add-annotations mv (meta mname))
(dotimes [i (count pmetas)]
(add-annotations mv (nth pmetas i) i))
(. mv visitEnd)))
(. cv visitEnd)
[iname (. cv toByteArray)]))
(defmacro gen-interface
"When compiling, generates compiled bytecode for an interface with
the given package-qualified :name (which, as all names in these
parameters, can be a string or symbol), and writes the .class file
to the *compile-path* directory. When not compiling, does nothing.
Options should be a set of key/value pairs, all except for :name are
optional:
1350 CHAPTER 11. CLJ/CLOJURE/
:name aname
[& options]
(let [options-map (apply hash-map options)
[cname bytecode] (generate-interface options-map)]
(if *compile-files*
(clojure.lang.Compiler/writeClassFile cname bytecode)
(.defineClass ^DynamicClassLoader
(deref clojure.lang.Compiler/LOADER)
(str (:name options-map)) bytecode options))))
(comment
(defn gen-and-load-class
"Generates and immediately loads the bytecode for the specified
class. Note that a class generated this way can be loaded only once
- the JVM supports only one class with a given name per
classloader. Subsequent to generation you can import it into any
desired namespaces just like any other class. See gen-class for a
description of the options."
{:added "1.0"}
[& options]
(let [options-map (apply hash-map options)
[cname bytecode] (generate-class options-map)]
(.. (clojure.lang.RT/getRootClassLoader)
(defineClass cname bytecode options))))
-
11.8. GVEC.CLJ 1351
11.8 gvec.clj
gvec.clj
\getchunk{Clojure Copyright}
(in-ns clojure.core)
(definterface IVecImpl
(^int tailoff [])
(arrayFor [^int i])
(pushTail [^int level
^clojure.core.VecNode parent
^clojure.core.VecNode tailnode])
(popTail [^int level node])
(newPath [edit ^int level node])
(doAssoc [^int level node ^int i val]))
(definterface ArrayManager
(array [^int size])
(^int alength [arr])
(aclone [arr])
(aget [arr ^int i])
(aset [arr ^int i val]))
clojure.lang.Indexed
(nth [_ i] (.aget am arr (+ off i)))
clojure.lang.IChunk
(dropFirst [_]
(if (= off end)
(throw (IllegalStateException. "dropFirst of empty chunk"))
(new ArrayChunk am arr (inc off) end)))
(reduce [_ f init]
1352 CHAPTER 11. CLJ/CLOJURE/
clojure.core.protocols.InternalReduce
(internal-reduce
[_ f val]
(loop [result val
aidx offset]
(if (< aidx (count vec))
(let [node (.arrayFor vec aidx)
result
(loop [result result node-idx (bit-and 0x1f aidx)]
(if (< node-idx (.alength am node))
(recur (f result (.aget am node node-idx))
(inc node-idx))
result))]
(recur result (bit-and 0xffe0 (+ aidx 32))))
result)))
clojure.lang.ISeq
(first [_] (.aget am anode offset))
(next [this]
(if (< (inc offset) (.alength am anode))
(new VecSeq am vec anode i (inc offset))
(.chunkedNext this)))
(more [this]
(let [s (.next this)]
(or s (clojure.lang.PersistentList/EMPTY))))
(cons [this o]
(clojure.lang.Cons. o this))
(count [this]
(loop [i 1
s (next this)]
(if s
(if (instance? clojure.lang.Counted s)
(+ i (.count s))
(recur (inc i) (next s)))
i)))
(equiv [this o]
(cond
(identical? this o) true
(or (instance? clojure.lang.Sequential o)
11.8. GVEC.CLJ 1353
clojure.lang.Seqable
(seq [this] this)
clojure.lang.IChunkedSeq
(chunkedFirst [_] (ArrayChunk. am anode offset (.alength am anode)))
(chunkedNext [_]
(let [nexti (+ i (.alength am anode))]
(when (< nexti (count vec))
(new VecSeq am vec (.arrayFor vec nexti) nexti 0))))
(chunkedMore [this]
(let [s (.chunkedNext this)]
(or s (clojure.lang.PersistentList/EMPTY)))))
;todo - cache
1354 CHAPTER 11. CLJ/CLOJURE/
(hashCode [this]
(loop [hash (int 1) i (int 0)]
(if (= i cnt)
hash
(let [val (.nth this i)]
(recur (unchecked-add-int (unchecked-multiply-int 31 hash)
(clojure.lang.Util/hash val))
(inc i))))))
clojure.lang.Counted
(count [_] cnt)
clojure.lang.IMeta
(meta [_] _meta)
clojure.lang.IObj
(withMeta [_ m] (new Vec am cnt shift root tail m))
clojure.lang.Indexed
(nth [this i]
(let [a (.arrayFor this i)]
(.aget am a (bit-and i (int 0x1f)))))
(nth [this i not-found]
(let [z (int 0)]
(if (and (>= i z) (< i (.count this)))
(.nth this i)
not-found)))
clojure.lang.IPersistentCollection
(cons [this val]
(if (< (- cnt (.tailoff this)) (int 32))
(let [new-tail (.array am (inc (.alength am tail)))]
(System/arraycopy tail 0 new-tail 0 (.alength am tail))
(.aset am new-tail (.alength am tail) val)
(new Vec am (inc cnt) shift root new-tail (meta this)))
(let [tail-node (VecNode. (.edit root) tail)]
(if (> (bit-shift-right cnt (int 5))
(bit-shift-left (int 1) shift)) ;overflow root?
(let [new-root (VecNode. (.edit root) (object-array 32))]
(doto ^objects (.arr new-root)
(aset 0 root)
(aset 1 (.newPath this (.edit root) shift tail-node)))
(new Vec am
(inc cnt)
(+ shift (int 5))
new-root
(let [tl (.array am 1)] (.aset am tl 0 val) tl)
(meta this)))
(new Vec am
(inc cnt)
11.8. GVEC.CLJ 1355
shift
(.pushTail this shift root tail-node)
(let [tl (.array am 1)]
(.aset am tl 0 val) tl) (meta this))))))
clojure.lang.IPersistentStack
(peek [this]
(when (> cnt (int 0))
(.nth this (dec cnt))))
(pop [this]
(cond
(zero? cnt)
(throw (IllegalStateException. "Cant pop empty vector"))
(= 1 cnt)
(new Vec am 0 5 EMPTY-NODE (.array am 0) (meta this))
(> (- cnt (.tailoff this)) 1)
(let [new-tail (.array am (dec (.alength am tail)))]
(System/arraycopy tail 0 new-tail 0 (.alength am new-tail))
(new Vec am (dec cnt) shift root new-tail (meta this)))
:else
(let [new-tail (.arrayFor this (- cnt 2))
new-root ^clojure.core.VecNode (.popTail this shift root)]
(cond
(nil? new-root)
(new Vec am (dec cnt) shift EMPTY-NODE new-tail (meta this))
(and (> shift 5) (nil? (aget ^objects (.arr new-root) 1)))
(new Vec am
(dec cnt)
(- shift 5)
(aget ^objects (.arr new-root) 0)
new-tail
(meta this))
:else
1356 CHAPTER 11. CLJ/CLOJURE/
(new Vec am
(dec cnt)
shift
new-root
new-tail
(meta this))))))
clojure.lang.IPersistentVector
(assocN [this i val]
(cond
(and (<= (int 0) i) (< i cnt))
(if (>= i (.tailoff this))
(let [new-tail (.array am (.alength am tail))]
(System/arraycopy tail 0 new-tail 0 (.alength am tail))
(.aset am new-tail (bit-and i (int 0x1f)) val)
(new Vec am cnt shift root new-tail (meta this)))
(new Vec am cnt shift
(.doAssoc this shift root i val) tail (meta this)))
(= i cnt) (.cons this val)
:else (throw (IndexOutOfBoundsException.))))
clojure.lang.Reversible
(rseq [this]
(if (> (.count this) 0)
(clojure.lang.APersistentVector$RSeq. this (dec (.count this)))
nil))
clojure.lang.Associative
(assoc [this k v]
(if (clojure.lang.Util/isInteger k)
(.assocN this k v)
(throw (IllegalArgumentException. "Key must be integer"))))
(containsKey [this k]
(and (clojure.lang.Util/isInteger k)
(<= 0 (int k))
(< (int k) cnt)))
(entryAt [this k]
(if (.containsKey this k)
(clojure.lang.MapEntry. k (.nth this (int k)))
nil))
clojure.lang.ILookup
(valAt [this k not-found]
(if (clojure.lang.Util/isInteger k)
(let [i (int k)]
(if (and (>= i 0) (< i cnt))
(.nth this i)
not-found))
not-found))
11.8. GVEC.CLJ 1357
clojure.lang.IFn
(invoke [this k]
(if (clojure.lang.Util/isInteger k)
(let [i (int k)]
(if (and (>= i 0) (< i cnt))
(.nth this i)
(throw (IndexOutOfBoundsException.))))
(throw (IllegalArgumentException. "Key must be integer"))))
clojure.lang.Seqable
(seq [this]
(if (zero? cnt)
nil
(VecSeq. am this (.arrayFor this 0) 0 0)))
clojure.core.IVecImpl
(tailoff [_]
(- cnt (.alength am tail)))
(arrayFor [this i]
(if (and (<= (int 0) i) (< i cnt))
(if (>= i (.tailoff this))
tail
(loop [node root level shift]
(if (zero? level)
(.arr node)
(recur
(aget ^objects (.arr node)
(bit-and (bit-shift-right i level) (int 0x1f)))
(- level (int 5))))))
(throw (IndexOutOfBoundsException.))))
ret))
java.lang.Comparable
(compareTo [this o]
(if (identical? this o)
0
(let [#^clojure.lang.IPersistentVector v
(cast clojure.lang.IPersistentVector o)
vcnt (.count v)]
11.8. GVEC.CLJ 1359
(cond
(< cnt vcnt) -1
(> cnt vcnt) 1
:else
(loop [i (int 0)]
(if (= i cnt)
0
(let [comp (clojure.lang.Util/compare (.nth this i)
(.nth v i))]
(if (= 0 comp)
(recur (inc i))
comp))))))))
java.lang.Iterable
(iterator [this]
(let [i (java.util.concurrent.atomic.AtomicInteger. 0)]
(reify java.util.Iterator
(hasNext [_] (< (.get i) cnt))
(next [_] (.nth this (dec (.incrementAndGet i))))
(remove [_] (throw (UnsupportedOperationException.))))))
java.util.Collection
(contains [this o] (boolean (some #(= % o) this)))
(containsAll [this c] (every? #(.contains this %) c))
(isEmpty [_] (zero? cnt))
(toArray [this] (into-array Object this))
(toArray [this arr]
(if (>= (count arr) cnt)
(do
(dotimes [i cnt]
(aset arr i (.nth this i)))
arr)
(into-array Object this)))
(size [_] cnt)
(add [_ o] (throw (UnsupportedOperationException.)))
(addAll [_ c] (throw (UnsupportedOperationException.)))
(clear [_] (throw (UnsupportedOperationException.)))
(^boolean remove [_ o] (throw (UnsupportedOperationException.)))
(removeAll [_ c] (throw (UnsupportedOperationException.)))
(retainAll [_ c] (throw (UnsupportedOperationException.)))
java.util.List
(get [this i] (.nth this i))
(indexOf [this o]
(loop [i (int 0)]
(cond
(== i cnt) -1
(= o (.nth this i)) i
:else (recur (inc i)))))
(lastIndexOf [this o]
1360 CHAPTER 11. CLJ/CLOJURE/
(defn vector-of
"Creates a new vector of a single primitive type t, where t is one
11.9. INSPECTOR.CLJ 1361
11.9 inspector.clj
inspector.clj
\getchunk{Clojure Copyright}
(defn inspect-tree
"creates a graphical (Swing) inspector on the supplied hierarchical
11.9. INSPECTOR.CLJ 1363
data"
{:added "1.0"}
[data]
(doto (JFrame. "Clojure Inspector")
(.add (JScrollPane. (JTree. (tree-model data))))
(.setSize 400 600)
(.setVisible true)))
(defn inspect-table
"creates a graphical (Swing) inspector on the supplied regular
data, which must be a sequential data structure of data structures
of equal length"
{:added "1.0"}
[data]
(doto (JFrame. "Clojure Inspector")
(.add (JScrollPane. (JTable. (old-table-model data))))
(.setSize 400 600)
(.setVisible true)))
(defn inspect
"creates a graphical (Swing) inspector on the supplied object"
{:added "1.0"}
[x]
(doto (JFrame. "Clojure Inspector")
(.add
(doto (JPanel. (BorderLayout.))
(.add (doto (JToolBar.)
(.add (JButton. "Back"))
(.addSeparator)
(.add (JButton. "List"))
(.add (JButton. "Table"))
(.add (JButton. "Bean"))
(.add (JButton. "Line"))
(.add (JButton. "Bar"))
(.addSeparator)
(.add (JButton. "Prev"))
(.add (JButton. "Next")))
BorderLayout/NORTH)
(.add
(JScrollPane.
(doto (JTable. (list-model (list-provider x)))
(.setAutoResizeMode JTable/AUTO_RESIZE_LAST_COLUMN)))
BorderLayout/CENTER)))
(.setSize 400 400)
(.setVisible true)))
(comment
(load-file "src/inspector.clj")
(refer inspector)
(inspect-tree {:a 1 :b 2 :c [1 2 3 {:d 4 :e 5 :f [6 7 8]}]})
(inspect-table [[1 2 3][4 5 6][7 8 9][10 11 12]])
)
11.10. BROWSE.CLJ 1365
11.10 browse.clj
browse.clj
\getchunk{Clojure Copyright}
(ns
^{:author "Christophe Grand",
:doc "Start a web browser from Clojure"}
clojure.java.browse
(:require [clojure.java.shell :as sh])
(:import (java.net URI)))
(defn- macosx? []
(-> "os.name" System/getProperty .toLowerCase
(.startsWith "mac os x")))
(defn- open-url-in-browser
"Opens url (a string) in the default system web browser. May not
work on all platforms. Returns url on success, nil if not
supported."
[url]
(try
(when (clojure.lang.Reflector/invokeStaticMethod "java.awt.Desktop"
"isDesktopSupported" (to-array nil))
(-> (clojure.lang.Reflector/invokeStaticMethod "java.awt.Desktop"
"getDesktop" (to-array nil))
(.browse (URI. url)))
url)
(catch ClassNotFoundException e
nil)))
(defn- open-url-in-swing
"Opens url (a string) in a Swing window."
[url]
; the implementation of this function resides in another
; namespace to be loaded "on demand"
; this fixes a bug on mac os x where the process turns into a GUI app
; see http://code.google.com/p/clojure-contrib/issues/detail?id=32
(require clojure.java.browse-ui)
((find-var clojure.java.browse-ui/open-url-in-swing) url))
(defn browse-url
1366 CHAPTER 11. CLJ/CLOJURE/
11.11 browseui.clj
browseui.clj
\getchunk{Clojure Copyright}
(ns
^{:author "Christophe Grand",
:doc "Helper namespace for clojure.java.browse.
Prevents console apps from becoming GUI unnecessarily."}
clojure.java.browse-ui)
(defn- open-url-in-swing
[url]
(let [htmlpane (javax.swing.JEditorPane. url)]
(.setEditable htmlpane false)
(.addHyperlinkListener htmlpane
(proxy [javax.swing.event.HyperlinkListener] []
(hyperlinkUpdate [#^javax.swing.event.HyperlinkEvent e]
(when
(= (.getEventType e)
(. javax.swing.event.HyperlinkEvent$EventType ACTIVATED))
(if (instance?
javax.swing.text.html.HTMLFrameHyperlinkEvent e)
(-> htmlpane .getDocument
(.processHTMLFrameHyperlinkEvent e))
(.setPage htmlpane (.getURL e)))))))
(doto (javax.swing.JFrame.)
(.setContentPane (javax.swing.JScrollPane. htmlpane))
(.setBounds 32 32 700 900)
(.show))))
-
11.12. IO.CLJ 1367
11.12 io.clj
io.clj
\getchunk{Clojure Copyright}
(ns
^{:author "Stuart Sierra, Chas Emerick, Stuart Halloway",
:doc "This file defines polymorphic I/O utility functions
for Clojure."}
clojure.java.io
(:import
(java.io Reader InputStream InputStreamReader PushbackReader
BufferedReader File OutputStream
OutputStreamWriter BufferedWriter Writer
FileInputStream FileOutputStream ByteArrayOutputStream
StringReader ByteArrayInputStream
BufferedInputStream BufferedOutputStream
CharArrayReader Closeable)
(java.net URI URL MalformedURLException Socket)))
(def
^{:doc "Type object for a Java primitive byte array."
:private true
}
byte-array-type (class (make-array Byte/TYPE 0)))
(def
^{:doc "Type object for a Java primitive char array."
:private true}
char-array-type (class (make-array Character/TYPE 0)))
(extend-protocol Coercions
nil
(as-file [_] nil)
(as-url [_] nil)
String
(as-file [s] (File. s))
(as-url [s] (URL. s))
1368 CHAPTER 11. CLJ/CLOJURE/
File
(as-file [f] f)
(as-url [f] (.toURL f))
URL
(as-url [u] u)
(as-file [u]
(if (= "file" (.getProtocol u))
(as-file (.getPath u))
(throw (IllegalArgumentException. (str "Not a file: " u)))))
URI
(as-url [u] (.toURL u))
(as-file [u] (as-file (as-url u))))
{:added "1.2"}
[x & opts]
(make-reader x (when opts (apply hash-map opts))))
properly closed."
{:added "1.2"}
[x & opts]
(make-output-stream x (when opts (apply hash-map opts))))
(def default-streams-impl
{:make-reader
(fn [x opts] (make-reader (make-input-stream x opts) opts))
:make-writer
(fn [x opts] (make-writer (make-output-stream x opts) opts))
:make-input-stream
(fn [x opts]
(throw (IllegalArgumentException.
(str "Cannot open <" (pr-str x) "> as an InputStream."))))
:make-output-stream
(fn [x opts]
(throw (IllegalArgumentException.
(str "Cannot open <" (pr-str x) "> as an OutputStream."))))})
(defn- inputstream->reader
[^InputStream is opts]
(make-reader (InputStreamReader. is (encoding opts)) opts))
(defn- outputstream->writer
[^OutputStream os opts]
(make-writer (OutputStreamWriter. os (encoding opts)) opts))
(extend BufferedInputStream
IOFactory
(assoc default-streams-impl
:make-input-stream (fn [x opts] x)
:make-reader inputstream->reader))
(extend InputStream
IOFactory
(assoc default-streams-impl
:make-input-stream (fn [x opts] (BufferedInputStream. x))
:make-reader inputstream->reader))
(extend Reader
IOFactory
11.12. IO.CLJ 1371
(assoc default-streams-impl
:make-reader (fn [x opts] (BufferedReader. x))))
(extend BufferedReader
IOFactory
(assoc default-streams-impl
:make-reader (fn [x opts] x)))
(extend Writer
IOFactory
(assoc default-streams-impl
:make-writer (fn [x opts] (BufferedWriter. x))))
(extend BufferedWriter
IOFactory
(assoc default-streams-impl
:make-writer (fn [x opts] x)))
(extend OutputStream
IOFactory
(assoc default-streams-impl
:make-output-stream (fn [x opts] (BufferedOutputStream. x))
:make-writer outputstream->writer))
(extend BufferedOutputStream
IOFactory
(assoc default-streams-impl
:make-output-stream (fn [x opts] x)
:make-writer outputstream->writer))
(extend File
IOFactory
(assoc default-streams-impl
:make-input-stream
(fn [^File x opts] (make-input-stream (FileInputStream. x) opts))
:make-output-stream
(fn [^File x opts]
(make-output-stream (FileOutputStream. x (append? opts)) opts))))
(extend URL
IOFactory
(assoc default-streams-impl
:make-input-stream (fn [^URL x opts]
(make-input-stream
(if (= "file" (.getProtocol x))
(FileInputStream. (.getPath x))
(.openStream x)) opts))
:make-output-stream
(fn [^URL x opts]
(if (= "file" (.getProtocol x))
1372 CHAPTER 11. CLJ/CLOJURE/
(extend URI
IOFactory
(assoc default-streams-impl
:make-input-stream
(fn [^URI x opts] (make-input-stream (.toURL x) opts))
:make-output-stream
(fn [^URI x opts] (make-output-stream (.toURL x) opts))))
(extend String
IOFactory
(assoc default-streams-impl
:make-input-stream (fn [^String x opts]
(try
(make-input-stream (URL. x) opts)
(catch MalformedURLException e
(make-input-stream (File. x) opts))))
:make-output-stream (fn [^String x opts]
(try
(make-output-stream (URL. x) opts)
(catch MalformedURLException err
(make-output-stream (File. x) opts))))))
(extend Socket
IOFactory
(assoc default-streams-impl
:make-input-stream
(fn [^Socket x opts]
(make-input-stream (.getInputStream x) opts))
:make-output-stream
(fn [^Socket x opts]
(make-output-stream (.getOutputStream x) opts))))
(extend byte-array-type
IOFactory
(assoc default-streams-impl
:make-input-stream
(fn [x opts] (make-input-stream (ByteArrayInputStream. x) opts))))
(extend char-array-type
IOFactory
(assoc default-streams-impl
:make-reader (fn [x opts] (make-reader (CharArrayReader. x) opts))))
(extend Object
IOFactory
default-streams-impl)
11.12. IO.CLJ 1373
(defmulti
#^{:doc "Internal helper for copy"
:private true
:arglists ([input output opts])}
do-copy
(fn [input output opts] [(type input) (type output)]))
(recur)))))))
(defn copy
"Copies input to output. Returns nil or throws IOException.
Input may be an InputStream, Reader, File, byte[], or String.
Output may be an OutputStream, Writer, or File.
(defn delete-file
"Delete file f. Raise an exception if it fails unless silently is
true."
{:added "1.2"}
[f & [silently]]
(or (.delete (file f))
silently
(throw (java.io.IOException. (str "Couldnt delete " f)))))
1376 CHAPTER 11. CLJ/CLOJURE/
(defn make-parents
"Given the same arg(s) as for file, creates all parent directories of
the file they represent."
{:added "1.2"}
[f & more]
(.mkdirs (.getParentFile ^File (apply file f more))))
11.13 javadoc.clj
javadoc.clj
\getchunk{Clojure Copyright}
(ns
^{:author "Christophe Grand, Stuart Sierra",
:doc "A repl helper to quickly open javadocs."}
clojure.java.javadoc
(:use [clojure.java.browse :only (browse-url)] )
(:import
(java.io File)))
"org.omg." *core-java-api*
"org.w3c.dom." *core-java-api*
"org.xml.sax." *core-java-api*
"org.apache.commons.codec."
"http://commons.apache.org/codec/api-release/"
"org.apache.commons.io."
"http://commons.apache.org/io/api-release/"
"org.apache.commons.lang."
"http://commons.apache.org/lang/api-release/")))
(defn add-local-javadoc
"Adds to the list of local Javadoc paths."
{:added "1.2"}
[path]
(dosync (commute *local-javadocs* conj path)))
(defn add-remote-javadoc
"Adds to the list of remote Javadoc URLs. package-prefix is the
beginning of the package name that has docs at this URL."
{:added "1.2"}
[package-prefix url]
(dosync (commute *remote-javadocs* assoc package-prefix url)))
(defn- javadoc-url
"Searches for a URL for the given class name. Tries
*local-javadocs* first, then *remote-javadocs*. Returns a string."
{:tag String,
:added "1.2"}
[^String classname]
(let [file-path (.replace classname \. File/separatorChar)
url-path (.replace classname \. \/)]
(if-let [file ^File
(first
(filter #(.exists ^File %)
(map #(File. (str %) (str file-path ".html"))
@*local-javadocs*)))]
(-> file .toURI str)
;; If no local file, try remote URLs:
(or (some (fn [[prefix url]]
(when (.startsWith classname prefix)
(str url url-path ".html")))
@*remote-javadocs*)
;; if *feeling-lucky* try a web search
(when *feeling-lucky*
(str *feeling-lucky-url* url-path ".html"))))))
(defn javadoc
"Opens a browser window displaying the javadoc for the argument.
Tries *local-javadocs* first, then *remote-javadocs*."
{:added "1.2"}
1378 CHAPTER 11. CLJ/CLOJURE/
[class-or-object]
(let [^Class c (if (instance? Class class-or-object)
class-or-object
(class class-or-object))]
(if-let [url (javadoc-url (.getName c))]
(browse-url url)
(println "Could not find Javadoc for" c))))
11.14 shell.clj
shell.clj
\getchunk{Clojure Copyright}
(ns
^{:author "Chris Houser, Stuart Halloway",
:doc "Conveniently launch a sub-process providing its stdin and
collecting its stdout"}
clojure.java.shell
(:use [clojure.java.io :only (as-file copy)])
(:import
(java.io OutputStreamWriter ByteArrayOutputStream StringWriter)
(java.nio.charset Charset)))
(defmacro with-sh-dir
"Sets the directory for use with sh, see sh for details."
{:added "1.2"}
[dir & forms]
(binding [*sh-dir* ~dir]
~@forms))
(defmacro with-sh-env
"Sets the environment for use with sh, see sh for details."
{:added "1.2"}
[env & forms]
(binding [*sh-env* ~env]
~@forms))
(defn- aconcat
"Concatenates arrays of given type."
[type & xs]
(let [target (make-array type (apply + (map count xs)))]
11.14. SHELL.CLJ 1379
(loop [i 0 idx 0]
(when-let [a (nth xs i nil)]
(System/arraycopy a 0 target idx (count a))
(recur (inc i) (+ idx (count a)))))
target))
(defn- parse-args
[args]
(let [default-encoding "UTF-8" ;; see sh doc string
default-opts {:out-enc default-encoding
:in-enc default-encoding
:dir *sh-dir*
:env *sh-env*}
[cmd opts] (split-with string? args)]
[cmd (merge default-opts (apply hash-map opts))]))
(defn- stream-to-bytes
[in]
(with-open [bout (ByteArrayOutputStream.)]
(copy in bout)
(.toByteArray bout)))
(defn- stream-to-string
([in] (stream-to-string in (.name (Charset/defaultCharset))))
([in enc]
(with-open [bout (StringWriter.)]
(copy in bout :encoding enc)
(.toString bout))))
(defn- stream-to-enc
[stream enc]
(if (= enc :bytes)
(stream-to-bytes stream)
(stream-to-string stream enc)))
(defn sh
"Passes the given strings to Runtime.exec() to launch a sub-process.
Options are
You can bind :env or :dir for multiple operations using with-sh-env
and with-sh-dir.
sh returns a map of
:exit => sub-processs exit code
:out => sub-processs stdout (as byte[] or String)
:err =>
sub-processs stderr (String via platform default encoding)"
{:added "1.2"}
[& args]
(let [[cmd opts] (parse-args args)
proc (.exec (Runtime/getRuntime)
^"[Ljava.lang.String;" (into-array cmd)
(as-env-strings (:env opts))
(as-file (:dir opts)))
{:keys [in in-enc out-enc]} opts]
(if in
(future
(if (instance? (class (byte-array 0)) in)
(with-open [os (.getOutputStream proc)]
(.write os ^"[B" in))
(with-open
[osw (OutputStreamWriter. (.getOutputStream proc)
^String in-enc)]
(.write osw ^String in))))
(.close (.getOutputStream proc)))
(with-open [stdout (.getInputStream proc)
stderr (.getErrorStream proc)]
(let [out (future (stream-to-enc stdout out-enc))
err (future (stream-to-string stderr))
exit-code (.waitFor proc)]
{:exit exit-code :out @out :err @err}))))
11.15. MAIN.CLJ 1381
(comment
11.15 main.clj
main.clj
\getchunk{Clojure Copyright}
(ns ^{:doc "Top-level main function for Clojure REPL and scripts."
:author "Stephen C. Gilardi and Rich Hickey"}
clojure.main
(:refer-clojure :exclude [with-bindings])
(:import (clojure.lang Compiler Compiler$CompilerException
LineNumberingPushbackReader RT))
(:use [clojure.repl :only (demunge root-cause stack-element-str)]))
(declare main)
(defmacro with-bindings
"Executes body in the context of thread-local bindings for several
vars that often need to be set!: *ns* *warn-on-reflection*
*math-context* *print-meta* *print-length* *print-level*
*compile-path* *command-line-args* *1 *2 *3 *e"
[& body]
(binding [*ns* *ns*
*warn-on-reflection* *warn-on-reflection*
*math-context* *math-context*
*print-meta* *print-meta*
*print-length* *print-length*
*print-level* *print-level*
1382 CHAPTER 11. CLJ/CLOJURE/
*compile-path*
(System/getProperty "clojure.compile.path" "classes")
*command-line-args* *command-line-args*
*unchecked-math* *unchecked-math*
*assert* *assert*
*1 nil
*2 nil
*3 nil
*e nil]
~@body))
(defn repl-prompt
"Default :prompt hook for repl"
[]
(printf "%s=> " (ns-name *ns*)))
(defn skip-if-eol
"If the next character on stream s is a newline, skips it, otherwise
leaves the stream untouched. Returns :line-start, :stream-end, or
:body to indicate the relative location of the next character on s.
The stream must either be an instance of LineNumberingPushbackReader
or duplicate its behavior of both supporting .unread and collapsing
all of CR, LF, and CRLF to a single \\newline."
[s]
(let [c (.read s)]
(cond
(= c (int \newline)) :line-start
(= c -1) :stream-end
:else (do (.unread s c) :body))))
(defn skip-whitespace
"Skips whitespace characters on stream s. Returns :line-start,
:stream-end, or :body to indicate the relative location of the next
character on s. Interprets comma as whitespace and semicolon as
comment to end of line. Does not interpret #! as comment to end
of line because only one character of lookahead is available.
The stream must either be an instance of LineNumberingPushbackReader
or duplicate its behavior of both supporting .unread and collapsing
all of CR, LF, and CRLF to a single \\newline."
[s]
(loop [c (.read s)]
(cond
(= c (int \newline)) :line-start
(= c -1) :stream-end
(= c (int \;)) (do (.readLine s) :line-start)
(or (Character/isWhitespace (char c))
(= c (int \,)))
(recur (.read s))
:else (do (.unread s c) :body))))
11.15. MAIN.CLJ 1383
(defn repl-read
"Default :read hook for repl. Reads from *in* which must either be
an instance of LineNumberingPushbackReader or duplicate its behavior
of both supporting .unread and collapsing all of CR, LF, and CRLF
into a single
\\newline. repl-read:
- skips whitespace, then
- returns request-prompt on start of line, or
- returns request-exit on end of stream, or
- reads an object from the input stream, then
- skips the next input character if its end of line, then
- returns the object."
[request-prompt request-exit]
(or ({:line-start request-prompt :stream-end request-exit}
(skip-whitespace *in*))
(let [input (read)]
(skip-if-eol *in*)
input)))
(defn repl-exception
"Returns the root cause of throwables"
[throwable]
(root-cause throwable))
(defn repl-caught
"Default :caught hook for repl"
[e]
(let [ex (repl-exception e)
tr (.getStackTrace ex)
el (when-not (zero? (count tr)) (aget tr 0))]
(binding [*out* *err*]
(println
(str (-> ex class .getSimpleName)
" " (.getMessage ex) " "
(when-not
(instance? clojure.lang.Compiler$CompilerException ex)
(str " "
(if el (stack-element-str el) "[trace missing]"))))))))
(defn repl
"Generic, reusable, read-eval-print loop. By default, reads from
*in*, writes to *out*, and prints exception summaries to *err*.
If you use the default :read hook, *in* must either be an instance
of LineNumberingPushbackReader or duplicate its behavior of both
supporting .unread and collapsing CR, LF, and CRLF into a single
\\newline. Options are sequential keyword-value pairs. Available
options and their defaults:
default: #()
(defn load-script
"Loads Clojure source from a file or resource given its path. Paths
beginning with @ or @/ are considered relative to classpath."
[^String path]
(if (.startsWith path "@")
(RT/loadResourceScript
(.substring path (if (.startsWith path "@/") 2 1)))
(Compiler/loadFile path)))
(defn- init-opt
"Load a script"
1386 CHAPTER 11. CLJ/CLOJURE/
[path]
(load-script path))
(defn- eval-opt
"Evals expressions in str, prints each non-nil result using prn"
[str]
(let [eof (Object.)
reader
(LineNumberingPushbackReader. (java.io.StringReader. str))]
(loop [input (read reader false eof)]
(when-not (= input eof)
(let [value (eval input)]
(when-not (nil? value)
(prn value))
(recur (read reader false eof)))))))
(defn- init-dispatch
"Returns the handler associated with an init opt"
[opt]
({"-i" init-opt
"--init" init-opt
"-e" eval-opt
"--eval" eval-opt} opt))
(defn- initialize
"Common initialize routine for repl, script, and null opts"
[args inits]
(in-ns user)
(set! *command-line-args* args)
(doseq [[opt arg] inits]
((init-dispatch opt) arg)))
(defn- main-opt
"Call the -main function from a namespace with string arguments from
the command line."
[[_ main-ns & args] inits]
(with-bindings
(initialize args inits)
(apply (ns-resolve (doto (symbol main-ns) require) -main) args)))
(defn- repl-opt
"Start a repl with args and inits. Print greeting if no eval options
were present"
[[_ & args] inits]
(when-not (some #(= eval-opt (init-dispatch (first %))) inits)
(println "Clojure" (clojure-version)))
(repl :init #(initialize args inits))
(prn)
(System/exit 0))
11.15. MAIN.CLJ 1387
(defn- script-opt
"Run a script from a file, resource, or standard in with args and
inits"
[[path & args] inits]
(with-bindings
(initialize args inits)
(if (= path "-")
(load-reader *in*)
(load-script path))))
(defn- null-opt
"No repl or script opt present, just bind args and run inits"
[args inits]
(with-bindings
(initialize args inits)))
(defn- help-opt
"Print help text for main"
[_ _]
(println (:doc (meta (var main)))))
(defn- main-dispatch
"Returns the handler associated with a main option"
[opt]
(or
({"-r" repl-opt
"--repl" repl-opt
"-m" main-opt
"--main" main-opt
nil null-opt
"-h" help-opt
"--help" help-opt
"-?" help-opt} opt)
script-opt))
(defn- legacy-repl
"Called by the clojure.lang.Repl.main stub to run a repl with args
specified the old way"
[args]
(println "WARNING: clojure.lang.Repl is deprecated.
Instead, use clojure.main like this:
java -cp clojure.jar clojure.main -i init.clj -r args...")
(let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
(repl-opt (concat ["-r"] args) (map vector (repeat "-i") inits))))
(defn- legacy-script
"Called by the clojure.lang.Script.main stub to run a script with
args specified the old way"
[args]
(println "WARNING: clojure.lang.Script is deprecated.
1388 CHAPTER 11. CLJ/CLOJURE/
(defn main
"Usage:
java -cp clojure.jar clojure.main [init-opt*] [main-opt] [arg*]
init options:
-i, --init path Load a file or resource
-e, --eval string Evaluate expressions in string; print non-nil
values
main options:
-m, --main ns-name Call the -main function from a namespace
with args
-r, --repl Run a repl
path Run a script from from a file or resource
- Run a script from standard input
-h, -?, --help Print this help message and exit
operation:
The init options may be repeated and mixed freely, but must appear
before any main option. The appearance of any eval option before
running a repl suppresses the usual repl greeting message:
\"Clojure ~(clojure-version)\".
11.16 parallel.clj
parallel.clj
\getchunk{Clojure Copyright}
(comment "
The parallel library wraps the ForkJoin library scheduled for
inclusion in JDK 7:
http://gee.cs.oswego.edu/dl/concurrency-interest/index.html
(defn- op [f]
(proxy [Ops$Op] []
(op [x] (f x))))
(defn par
"Creates a parallel array from coll. ops, if supplied, perform
on-the-fly filtering or transformations during parallel realization
or calculation. ops form a chain, and bounds must precede filters,
must precede maps. ops must be a set of keyword value pairs of the
following forms:
:filter pred
:filter-index pred2
:map f
:map-index f2
([coll]
(if (instance? ParallelArrayWithMapping coll)
coll
(. ParallelArray createUsingHandoff
(to-array coll)
(. ParallelArray defaultExecutor))))
([coll & ops]
(reduce
(fn [pa [op args]]
(cond
(= op :bound)
(. pa withBounds (args 0) (args 1))
(= op :filter)
(. pa withFilter (predicate args))
(= op :filter-with)
(. pa withFilter (binary-predicate (args 0)) (par (args 1)))
(= op :filter-index)
(. pa withIndexedFilter (int-and-object-predicate args))
(= op :map)
(. pa withMapping (parallel/op args))
(= op :map-with)
(. pa withMapping (binary-op (args 0)) (par (args 1)))
(= op :map-index)
(. pa withIndexedMapping (int-and-object-to-object args))
:else (throw (Exception. (str "Unsupported par op: " op)))))
(par coll)
(partition 2 ops))))
1392 CHAPTER 11. CLJ/CLOJURE/
(defn pmax
"Returns the maximum element, presuming Comparable elements, unless
a Comparator comp is supplied"
([coll] (. (par coll) max))
([coll comp] (. (par coll) max comp)))
(defn pmin
"Returns the minimum element, presuming Comparable elements, unless
a Comparator comp is supplied"
([coll] (. (par coll) min))
([coll comp] (. (par coll) min comp)))
(defn psummary
"Returns a map of summary statistics (min. max, size, min-index,
max-index, presuming Comparable elements, unless a Comparator
comp is supplied"
([coll] (summary-map (. (par coll) summary)))
([coll comp] (summary-map (. (par coll) summary comp))))
(defn preduce
"Returns the reduction of the realized elements of coll
using function f. Note f will not necessarily be called
consecutively, and so must be commutative. Also note that
(f base an-element) might be performed many times, i.e. base is not
an initial value as with sequential reduce."
[f base coll]
(. (par coll) (reduce (reducer f) base)))
(defn- pall
"Realizes a copy of the coll as a parallel array, with any
bounds/filters/maps applied"
[coll]
(if (instance? ParallelArrayWithMapping coll)
(. coll all)
11.16. PARALLEL.CLJ 1393
(par coll)))
(defn pvec
"Returns the realized contents of the parallel array pa as
a Clojure vector"
[pa] (pa-to-vec (pall pa)))
(defn pdistinct
"Returns a parallel array of the distinct elements of coll"
[coll]
(pa-to-vec (. (pall coll) allUniqueElements)))
(defn psort
"Returns a new vector consisting of the realized items in coll,
sorted, presuming Comparable elements, unless a Comparator comp
is supplied"
([coll] (pa-to-vec (. (pall coll) sort)))
([coll comp] (pa-to-vec (. (pall coll) sort comp))))
(defn pfilter-nils
"Returns a vector containing the non-nil (realized) elements of coll"
[coll]
(pa-to-vec (. (pall coll) removeNulls)))
(defn pfilter-dupes
"Returns a vector containing the (realized) elements of coll,
without any consecutive duplicates"
[coll]
(pa-to-vec (. (pall coll) removeConsecutiveDuplicates)))
(comment
(load-file "src/parallel.clj")
(refer parallel)
(pdistinct [1 2 3 2 1])
;(pcumulate [1 2 3 2 1] + 0) ;broken, not exposed
(def a (make-array Object 1000000))
(dotimes i (count a)
(aset a i (rand-int i)))
(time (reduce + 0 a))
(time (preduce + 0 a))
(time (count (distinct a)))
(time (count (pdistinct a)))
(preduce + 0 [1 2 3 2 1])
(preduce + 0 (psort a))
1394 CHAPTER 11. CLJ/CLOJURE/
(preduce + 0
(par [11 2 3 2]
:filter-with [< [110 2 33 2]]))
11.17 clformat.clj
clformat.clj
\getchunk{Clojure Copyright}
(in-ns clojure.pprint)
(defn cl-format
"An implementation of a Common Lisp compatible format function.
cl-format formats its arguments to an output stream or string based
on the format control string given. It supports sophisticated
formatting of structured data.
For example:
(let [results [46 38 22]]
(cl-format true
\"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\"
(count results) results))
Prints to *out*:
There are 3 results: 46, 38, 22
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Argument navigators manage the argument list
;;; as the format statement moves through the list
;;; (possibly going forwards and backwards as it does so)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- init-navigator
"Create a new arg-navigator from the sequence with the position
set to 0"
{:skip-wiki true}
[s]
(let [s (seq s)]
(struct arg-navigator s s 0)))
(declare relative-reposition)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; When looking at the parameter list, we may need to manipulate
;;; the argument list as well (for V and # parameter types).
;;; We hide all of this behind a function, but clients need to
;;; manage changing arg navigator
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[raw-val navigator]
(= raw-val :parameter-from-args)
(next-arg navigator)
(= raw-val :remaining-arg-count)
[(count (:rest navigator)) navigator]
true
[raw-val navigator])]
[[param [real-param offset]] new-navigator]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1398 CHAPTER 11. CLJ/CLOJURE/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Common handling code for ~A and ~S
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare opt-base-str)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for the integer directives ~D, ~X, ~O, ~B and some
;;; of ~R
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- integral?
"returns true if a number is actually an integer (that is, has no
fractional part)"
[x]
(cond
(integer? x) true
(decimal? x) ; true iff no fractional part
(>= (.ulp (.stripTrailingZeros (bigdec 0))) 1)
(float? x) (= x (Math/floor x))
(ratio? x) (let [^clojure.lang.Ratio r x]
(= 0 (rem (.numerator r) (.denominator r))))
:else false))
(defn- remainders
"Return the list of remainders (essentially the digits) of val
in the given base"
[base val]
(reverse
(first
(consume #(if (pos? %)
[(rem % base) (quot % base)]
[nil nil])
val))))
(defn- opt-base-str
"Return val as a string in the given base, using
clojure.core/format if supported
for improved performance"
[base val]
(let [format-str (get java-base-formats base)]
(if (and format-str
(integer? val)
(not (instance? clojure.lang.BigInt val)))
(clojure.core/format format-str val)
(base-str base val))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for english formats (~R and ~:R)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- format-simple-cardinal
"Convert a number less than 1000 to a cardinal english string"
[num]
(let [hundreds (quot num 100)
tens (rem num 100)]
(str
1402 CHAPTER 11. CLJ/CLOJURE/
(defn- add-english-scales
"Take a sequence of parts, add scale numbers (e.g., million) and
combine into a string offset is a factor of 10^3 to multiply by"
[parts offset]
(let [cnt (count parts)]
(loop [acc []
pos (dec cnt)
this (first parts)
remainder (next parts)]
(if (nil? remainder)
(str (apply str (interpose ", " acc))
(if (and (not (empty? this)) (not (empty? acc))) ", ")
this
(if (and (not (empty? this)) (pos? (+ pos offset)))
(str " " (nth english-scale-numbers (+ pos offset)))))
(recur
(if (empty? this)
acc
(conj acc
(str this " " (nth english-scale-numbers (+ pos offset)))))
(dec pos)
(first remainder)
(next remainder))))))
(defn- format-simple-ordinal
"Convert a number less than 1000 to a ordinal english string
Note this should only be used for the last one in the sequence"
[num]
(let [hundreds (quot num 100)
tens (rem num 100)]
(str
(if (pos? hundreds)
(str (nth english-cardinal-units hundreds) " hundred"))
(if (and (pos? hundreds) (pos? tens)) " ")
(if (pos? tens)
(if (< tens 20)
(nth english-ordinal-units tens)
(let [ten-digit (quot tens 10)
unit-digit (rem tens 10)]
(if (and (pos? ten-digit) (not (pos? unit-digit)))
(nth english-ordinal-tens ten-digit)
(str
(if (pos? ten-digit) (nth english-cardinal-tens ten-digit))
(if (and (pos? ten-digit) (pos? unit-digit)) "-")
(if (pos? unit-digit)
(nth english-ordinal-units unit-digit))))))
(if (pos? hundreds) "th")))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for roman numeral formats (~@R and ~@:R)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- format-roman
"Format a roman numeral using the specified look-up table"
[table params navigator offsets]
(let [[arg navigator] (next-arg navigator)]
(if (and (number? arg) (> arg 0) (< arg 4000))
(let [digits (remainders 10 arg)]
(loop [acc []
pos (dec (count digits))
digits digits]
(if (empty? digits)
11.17. CLFORMAT.CLJ 1405
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for character formats (~C)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for real number formats
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- float-parts
"Take care of leading and trailing zeros in decomposed floats"
[f]
(let [[m ^String e] (float-parts-base f)
11.17. CLFORMAT.CLJ 1407
m1 (rtrim m \0)
m2 (ltrim m1 \0)
delta (- (count m1) (count m2))
^String e (if (and (pos? (count e)) (= (nth e 0) \+))
(subs e 1) e)]
(if (empty? m2)
["0" 0]
[m2 (- (Integer/valueOf e) delta)])))
(defn- round-str [m e d w]
(if (or d w)
(let [len (count m)
round-pos (if d (+ e d 1))
round-pos (if (and w (< (inc e) (dec w))
(or (nil? round-pos)
(< (dec w) round-pos)))
(dec w)
round-pos)
[m1 e1 round-pos len] (if (= round-pos 0)
[(str "0" m) (inc e) 1 (inc len)]
[m e round-pos len])]
(if round-pos
(if (neg? round-pos)
["0" 0 false]
(if (> len round-pos)
(let [round-char (nth m1 round-pos)
^String result (subs m1 0 round-pos)]
(if (>= (int round-char) (int \5))
(let [result-val (Integer/valueOf result)
leading-zeros
(subs result 0
(min (prefix-count result \0)
(- round-pos 1)))
round-up-result
(str leading-zeros
(String/valueOf (+ result-val
(if (neg? result-val) -1 1))))
expanded (> (count round-up-result)
(count result))]
[round-up-result e1 expanded])
[result e1 false]))
[m e false]))
[m e false]))
[m e false]))
(defn- expand-fixed [m e d]
(let [m1 (if (neg? e) (str (apply str (repeat (dec (- e)) \0)) m) m)
len (count m1)
target-len (if d (+ e d 1) (inc e))]
(if (< len target-len)
1408 CHAPTER 11. CLJ/CLOJURE/
(defn- insert-decimal
"Insert the decimal point at the right spot in the number to
match an exponent"
[m e]
(if (neg? e)
(str "." m)
(let [loc (inc e)]
(str (subs m 0 loc) "." (subs m loc)))))
(defn- get-fixed [m e d]
(insert-decimal (expand-fixed m e d) e))
(defn- insert-scaled-decimal
"Insert the decimal point at the right spot in the number to
match an exponent"
[m k]
(if (neg? k)
(str "." m)
(str (subs m 0 k) "." (subs m k))))
scaled-mantissa 0
(cond
(= k 0) (dec d)
(pos? k) d
(neg? k) (dec d))
(if w-mantissa (- w-mantissa (if add-sign 1 0))))
full-mantissa (insert-scaled-decimal rounded-mantissa k)
append-zero (and (= k (count rounded-mantissa)) (nil? d))]
(if (not incr-exp)
(if w
(let [len (+ (count full-mantissa) exp-width)
signed-len (if add-sign (inc len) len)
prepend-zero (and prepend-zero (not (= signed-len w)))
full-len (if prepend-zero (inc signed-len) signed-len)
append-zero (and append-zero (< full-len w))]
(if (and (or (> full-len w) (and e (> (- exp-width 2) e)))
(:overflowchar params))
(print (apply str (repeat w (:overflowchar params))))
(print (str
(apply str
(repeat
(- w full-len (if append-zero 1 0) )
(:padchar params)))
(if add-sign (if (neg? arg) \- \+))
(if prepend-zero "0")
full-mantissa
(if append-zero "0")
scaled-exp-str))))
(print (str
(if add-sign (if (neg? arg) \- \+))
(if prepend-zero "0")
full-mantissa
(if append-zero "0")
scaled-exp-str)))
(recur [rounded-mantissa (inc exp)]))))
navigator))
ww (if w (- w ee))
d (if d d (max (count mantissa) (min n 7)))
dd (- d n)]
(if (<= 0 dd d)
(let [navigator (fixed-float {:w ww, :d dd, :k 0,
:overflowchar (:overflowchar params),
:padchar (:padchar params),
:at (:at params)}
navigator offsets)]
(print (apply str (repeat ee \space)))
navigator)
(exponential-float params navigator offsets))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for the ~[...~] conditional construct in its
;;; different flavors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[arg navigator]
(if arg [arg arg-navigator] (next-arg arg-navigator))
clauses (:clauses params)
clause (if (or (neg? arg) (>= arg (count clauses)))
(first (:else params))
(nth clauses arg))]
(if clause
(execute-sub-format clause navigator (:base-args params))
navigator)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for the ~{...~} iteration construct in its
;;; different flavors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ~@{...~} with the at sign uses the main argument list as the a
;; rguments to the iterations is consumed by all the iterations
(defn- iterate-main-list [params navigator offsets]
(let [max-count (:max-iterations params)
param-clause (first (:clauses params))
1414 CHAPTER 11. CLJ/CLOJURE/
;; ~@:{...~} with both colon and at sign uses the main argument list
;; as a set of sublists, one of which is consumed with each iteration
(defn- iterate-main-sublists [params navigator offsets]
(let [max-count (:max-iterations params)
param-clause (first (:clauses params))
[clause navigator] (if (empty? param-clause)
(get-format-arg navigator)
[param-clause navigator])
]
(loop [count 0
navigator navigator]
(if (or (and (empty? (:rest navigator))
(or (not (:colon (:right-params params)))
(> count 0)))
(and max-count (>= count max-count)))
navigator
(let [[sublist navigator] (next-arg-or-nil navigator)
iter-result
(execute-sub-format clause
(init-navigator sublist) navigator)]
(if (= :colon-up-arrow (first iter-result))
navigator
(recur (inc count) navigator)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The ~< directive has two completely different meanings
;;; in the ~<...~> form it does justification, but with
;;; ~<...~:> it represents the logical block operation of the
11.17. CLFORMAT.CLJ 1415
(declare format-logical-block)
(declare justify-clauses)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for the ~<...~> justification directive
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for case modification with ~(...~).
;;; We do this by wrapping the underlying writer with
;;; a special writer to do the appropriate modification. This
;;; allows us to support arbitrary-sized output and sources
;;; that may block.
11.17. CLFORMAT.CLJ 1417
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- downcase-writer
"Returns a proxy that wraps writer, converting all characters to
lower case"
[^java.io.Writer writer]
(proxy [java.io.Writer] []
(close [] (.close writer))
(flush [] (.flush writer))
(write ([^chars cbuf ^Integer off ^Integer len]
(.write writer cbuf off len))
([x]
(condp = (class x)
String
(let [s ^String x]
(.write writer (.toLowerCase s)))
Integer
(let [c ^Character x]
(.write writer
(int (Character/toLowerCase (char c))))))))))
(defn- upcase-writer
"Returns a proxy that wraps writer, converting all characters to
upper case"
[^java.io.Writer writer]
(proxy [java.io.Writer] []
(close [] (.close writer))
(flush [] (.flush writer))
(write ([^chars cbuf ^Integer off ^Integer len]
(.write writer cbuf off len))
([x]
(condp = (class x)
String
(let [s ^String x]
(.write writer (.toUpperCase s)))
Integer
(let [c ^Character x]
(.write writer
(int (Character/toUpperCase (char c))))))))))
(defn- capitalize-string
"Capitalizes the words in a string. If first? is false, dont
capitalize the first character of the string even if its a letter."
[s first?]
(let [^Character f (first s)
s (if (and first? f (Character/isLetter f))
(str (Character/toUpperCase f) (subs s 1))
s)]
1418 CHAPTER 11. CLJ/CLOJURE/
(apply str
(first
(consume
(fn [s]
(if (empty? s)
[nil nil]
(let [m (re-matcher #"\W\w" s)
match (re-find m)
offset (and match (inc (.start m)))]
(if offset
[(str (subs s 0 offset)
(Character/toUpperCase
^Character (nth s offset)))
(subs s (inc offset))]
[s nil]))))
s)))))
(defn- capitalize-word-writer
"Returns a proxy that wraps writer, captializing all words"
[^java.io.Writer writer]
(let [last-was-whitespace? (ref true)]
(proxy [java.io.Writer] []
(close [] (.close writer))
(flush [] (.flush writer))
(write
([^chars cbuf ^Integer off ^Integer len]
(.write writer cbuf off len))
([x]
(condp = (class x)
String
(let [s ^String x]
(.write writer
^String (capitalize-string (.toLowerCase s)
@last-was-whitespace?))
(dosync
(ref-set last-was-whitespace?
(Character/isWhitespace
^Character (nth s (dec (count s)))))))
Integer
(let [c (char x)]
(let [mod-c (if @last-was-whitespace?
(Character/toUpperCase (char x))
c)]
(.write writer (int mod-c))
(dosync
(ref-set last-was-whitespace?
(Character/isWhitespace (char x))))))))))))
(defn- init-cap-writer
11.17. CLFORMAT.CLJ 1419
Integer
(let [c ^Character (char x)]
(if (and (not @capped) (Character/isLetter c))
(do
(dosync (ref-set capped true))
(.write writer (int (Character/toUpperCase c))))
(.write writer
(int (Character/toLowerCase c)))))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; If necessary, wrap the writer in a PrettyWriter object
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-pretty-writer
"Returns the java.io.Writer passed in wrapped in a pretty writer
proxy, unless its already a pretty writer. Generally, it is
unneccesary to call this function, since pprint, write, and
cl-format all call it if they need to. However if you want the
1420 CHAPTER 11. CLJ/CLOJURE/
It prints a table of squares and cubes for the numbers from 1 to 10:
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000"
{:added "1.2"}
[writer]
(if (pretty-writer? writer)
writer
(pretty-writer writer *print-right-margin* *print-miser-width*)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for column-aware operations ~&, ~T
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn fresh-line
"Make a newline if *out* is not already at the beginning of the line.
If *out* is not a pretty writer (which keeps track of columns), this
function always outputs a newline."
{:added "1.2"}
[]
(if (instance? clojure.lang.IDeref *out*)
(if (not (= 0 (get-column (:base @@*out*))))
(prn))
(prn)))
11.17. CLFORMAT.CLJ 1421
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Support for accessing the pretty printer from a format
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The table of directives we support, each with its params,
;;; properties, and the compilation function
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defdirectives
(\A
[ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer]
:padchar [\space Character] ]
#{ :at :colon :both} {}
#(format-ascii print-str %1 %2 %3))
(\S
[ :mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer]
:padchar [\space Character] ]
#{ :at :colon :both} {}
#(format-ascii pr-str %1 %2 %3))
(\D
[ :mincol [0 Integer] :padchar [\space Character]
11.17. CLFORMAT.CLJ 1423
(\B
[ :mincol [0 Integer] :padchar [\space Character]
:commachar [\, Character] :commainterval [ 3 Integer]]
#{ :at :colon :both } {}
#(format-integer 2 %1 %2 %3))
(\O
[ :mincol [0 Integer] :padchar [\space Character]
:commachar [\, Character] :commainterval [ 3 Integer]]
#{ :at :colon :both } {}
#(format-integer 8 %1 %2 %3))
(\X
[ :mincol [0 Integer] :padchar [\space Character]
:commachar [\, Character] :commainterval [ 3 Integer]]
#{ :at :colon :both } {}
#(format-integer 16 %1 %2 %3))
(\R
[:base [nil Integer] :mincol [0 Integer]
:padchar [\space Character] :commachar [\, Character]
:commainterval [ 3 Integer]]
#{ :at :colon :both } {}
(do
(cond ; ~R is overloaded with bizareness
(first (:base params)) #(format-integer (:base %1) %1 %2 %3)
(and (:at params) (:colon params)) #(format-old-roman %1 %2 %3)
(:at params) #(format-new-roman %1 %2 %3)
(:colon params) #(format-ordinal-english %1 %2 %3)
true #(format-cardinal-english %1 %2 %3))))
(\P
[ ]
#{ :at :colon :both } {}
(fn [params navigator offsets]
(let [navigator (if (:colon params)
(relative-reposition navigator -1)
navigator)
strs (if (:at params) ["y" "ies"] ["" "s"])
[arg navigator] (next-arg navigator)]
(print (if (= arg 1) (first strs) (second strs)))
navigator)))
(\C
[:char-format [nil Character]]
#{ :at :colon :both } {}
1424 CHAPTER 11. CLJ/CLOJURE/
(cond
(:colon params) pretty-character
(:at params) readable-character
:else plain-character))
(\F
[ :w [nil Integer] :d [nil Integer] :k [0 Integer]
:overflowchar [nil Character] :padchar [\space Character] ]
#{ :at } {}
fixed-float)
(\E
[ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]
:overflowchar [nil Character] :padchar [\space Character]
:exponentchar [nil Character] ]
#{ :at } {}
exponential-float)
(\G
[ :w [nil Integer] :d [nil Integer] :e [nil Integer] :k [1 Integer]
:overflowchar [nil Character] :padchar [\space Character]
:exponentchar [nil Character] ]
#{ :at } {}
general-float)
(\$
[ :d [2 Integer] :n [1 Integer] :w [0 Integer]
:padchar [\space Character]]
#{ :at :colon :both} {}
dollar-float)
(\%
[ :count [1 Integer] ]
#{ } {}
(fn [params arg-navigator offsets]
(dotimes [i (:count params)]
(prn))
arg-navigator))
(\&
[ :count [1 Integer] ]
#{ :pretty } {}
(fn [params arg-navigator offsets]
(let [cnt (:count params)]
(if (pos? cnt) (fresh-line))
(dotimes [i (dec cnt)]
(prn)))
arg-navigator))
(\|
11.17. CLFORMAT.CLJ 1425
[ :count [1 Integer] ]
#{ } {}
(fn [params arg-navigator offsets]
(dotimes [i (:count params)]
(print \formfeed))
arg-navigator))
(\~
[ :n [1 Integer] ]
#{ } {}
(fn [params arg-navigator offsets]
(let [n (:n params)]
(print (apply str (repeat n \~)))
arg-navigator)))
(\T
[ :colnum [1 Integer] :colinc [1 Integer] ]
#{ :at :pretty } {}
(if (:at params)
#(relative-tabulation %1 %2 %3)
#(absolute-tabulation %1 %2 %3)))
(\*
[ :n [1 Integer] ]
#{ :colon :at } {}
(fn [params navigator offsets]
(let [n (:n params)]
(if (:at params)
(absolute-reposition navigator n)
(relative-reposition navigator (if (:colon params) (- n) n)))
)))
(\?
[ ]
#{ :at } {}
(if (:at params)
(fn [params navigator offsets] ; args from main arg list
(let [[subformat navigator] (get-format-arg navigator)]
(execute-sub-format subformat navigator
(:base-args params))))
(fn [params navigator offsets] ; args from sub-list
(let [[subformat navigator] (get-format-arg navigator)
1426 CHAPTER 11. CLJ/CLOJURE/
(\(
[ ]
#{ :colon :at :both} { :right \), :allows-separator nil, :else nil }
(let [mod-case-writer (cond
(and (:at params) (:colon params))
upcase-writer
(:colon params)
capitalize-word-writer
(:at params)
init-cap-writer
:else
downcase-writer)]
#(modify-case mod-case-writer %1 %2 %3)))
(\[
[ :selector [nil Integer] ]
#{ :colon :at } { :right \], :allows-separator true, :else :last }
(cond
(:colon params)
boolean-conditional
(:at params)
check-arg-conditional
true
choice-conditional))
(\{
[ :max-iterations [nil Integer] ]
#{ :colon :at :both} { :right \}, :allows-separator false }
(cond
(and (:at params) (:colon params))
iterate-main-sublists
11.17. CLFORMAT.CLJ 1427
(:colon params)
iterate-list-of-sublists
(:at params)
iterate-main-list
true
iterate-sublist))
(\<
[:mincol [0 Integer] :colinc [1 Integer] :minpad [0 Integer]
:padchar [\space Character]]
#{:colon :at :both :pretty} { :right \>,
:allows-separator true, :else :first }
logical-block-or-justify)
arg1
(if (= arg1 0) [exit navigator] navigator)
(\W
[]
#{:at :colon :both} {}
(if (or (:at params) (:colon params))
1428 CHAPTER 11. CLJ/CLOJURE/
(\_
[]
#{:at :colon :both} {}
conditional-newline)
(\I
[:n [0 Integer]]
#{:colon} {}
set-indent)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Code to manage the parameters and flags associated with each
;;; directive in the format string.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- translate-param
"Translate the string representation of a param to the internalized
representation"
[[^String p offset]]
[(cond
(= (.length p) 0) nil
(and (= (.length p) 1)
(contains? #{\v \V} (nth p 0))) :parameter-from-args
(and (= (.length p) 1) (= \# (nth p 0))) :remaining-arg-count
(and (= (.length p) 2) (= \ (nth p 0))) (nth p 1)
true (new Integer p))
offset])
(format-error (str
"Cannot combine \"@\" and \":\" flags for format directive \""
(:directive def) "\"")
(min (nth (:colon flags) 1) (nth (:at flags) 1))))))
(defn- map-params
"Takes a directive definition and the list of actual parameters
and a map of flags and returns a map of the parameters and flags
with defaults filled in. We check to make sure that there are the
right types and number of parameters as well."
[def params flags offset]
(check-flags def flags)
(if (> (count params) (count (:params def)))
(format-error
(cl-format
nil
(str
"Too many parameters for directive \"~C\": ~D~:* "
"~[were~;was~:;were~] specified but only ~D~:* "
"~[are~;is~:;are~] allowed")
(:directive def) (count params) (count (:params def)))
(second (first params))))
(doall
(map #(let [val (first %1)]
(if (not (or (nil? val) (contains? special-params val)
(instance? (second (second %2)) val)))
(format-error (str "Parameter " (name (first %2))
" has bad type in directive \"" (:directive def) "\": "
(class val))
(second %1))) )
params (:params def)))
(declare collect-clauses)
(right-bracket this)
(process-bracket this remainder)
(else-separator? this)
[nil [:else nil (:params this) remainder]]
(separator? this)
[nil [:separator nil nil remainder]] ;; TODO: check to make
;; sure that there are
;; no params on ~;
true
[this remainder]))))
remainder))
(= type :else)
(cond
(:else clause-map)
(format-error
"Two else clauses (\"~:;\") inside bracket construction."
offset)
(= type :separator)
(cond
saw-else
(format-error (str
"A plain clause (with \"~;\") follows an else "
"clause (\"~:;\") inside bracket construction.") offset)
true
[true [(merge-with concat clause-map { :clauses [clause] })
false remainder]]))))
[{ :clauses [] } false remainder])))
(defn- process-nesting
"Take a linearly compiled format and process the bracket directives
to give it the appropriate tree structure"
[format]
(first
(consume
(fn [remainder]
(let [this (first remainder)
remainder (next remainder)
bracket (:bracket-info (:def this))]
(if (:right bracket)
(process-bracket this remainder)
[this remainder])))
format)))
(defn- compile-format
"Compiles format-str into a compiled format which can be used as
an argument to cl-format just like a plain format string. Use this
function for improved performance when youre using the same format
string repeatedly"
[ format-str ]
; (prlabel compiling format-str)
1434 CHAPTER 11. CLJ/CLOJURE/
(defn- needs-pretty
"determine whether a given compiled format has any directives
that depend on the column number or pretty printing"
[format]
(loop [format format]
(if (empty? format)
false
(if (or (:pretty (:flags (:def (first format))))
(some needs-pretty
(first (:clauses (:params (first format)))))
(some needs-pretty
(first (:else (:params (first format))))))
true
(recur (next format))))))
(defn- execute-format
"Executes the format with the arguments."
{:skip-wiki true}
([stream format args]
(let [^java.io.Writer real-stream
(cond
(not stream) (java.io.StringWriter.)
(true? stream) *out*
:else stream)
^java.io.Writer wrapped-stream
(if (and (needs-pretty format)
(not (pretty-writer? real-stream)))
(get-pretty-writer real-stream)
real-stream)]
(binding [*out* wrapped-stream]
(try
(execute-format format args)
11.17. CLFORMAT.CLJ 1435
(finally
(if-not (identical? real-stream wrapped-stream)
(.flush wrapped-stream))))
(if (not stream) (.toString real-stream)))))
([format args]
(map-passing-context
(fn [element context]
(if (abort? context)
[nil context]
(let [[params args] (realize-parameter-list
(:params element) context)
[params offsets] (unzip-map params)
params (assoc params :base-args args)]
[nil (apply (:func element) [params args offsets])])))
args
format)
nil))
;;; This is a bad idea, but it prevents us from leaking private symbols
;;; This should all be replaced by really compiled formats anyway.
(def ^{:private true} cached-compile (memoize compile-format))
(defmacro formatter
"Makes a function which can directly run format-in. The function is
fn [stream & args] ... and returns nil unless the stream is nil (meaning
output to a string) in which case it returns the resulting string.
(defmacro formatter-out
"Makes a function which can directly run format-in. The function is
fn [& args] ... and returns nil. This version of the formatter macro is
designed to be used with *out* set to an appropriate Writer. In
particular, this is meant to be used as part of a pretty printer
dispatch method.
1436 CHAPTER 11. CLJ/CLOJURE/
11.18 columnwriter.clj
columnwriter.clj
\getchunk{Clojure Copyright}
;;; column_writer.clj -- part of the pretty printer for Clojure
(in-ns clojure.pprint)
(defn- column-writer
([writer] (column-writer writer *default-page-width*))
([writer max-columns]
(let [fields (ref {:max max-columns, :cur 0,
:line 0 :base writer})]
(proxy [Writer IDeref] []
(deref [] fields)
(write
([^chars cbuf ^Integer off ^Integer len]
(let [^Writer writer (get-field this :base)]
(.write writer cbuf off len)))
([x]
(condp = (class x)
String
(let [^String s x
nl (.lastIndexOf s (int \newline))]
(dosync (if (neg? nl)
(set-field this :cur
(+ (get-field this :cur) (count s)))
(do
(set-field this :cur (- (count s) nl 1))
(set-field this :line
(+ (get-field this :line)
(count (filter #(= % \newline) s)))))))
(.write ^Writer (get-field this :base) s))
Integer
(c-write-char this x)
Long
(c-write-char this x))))))))
-
1438 CHAPTER 11. CLJ/CLOJURE/
11.19 dispatch.clj
dispatch.clj
\getchunk{Clojure Copyright}
;; dispatch.clj -- part of the pretty printer for Clojure
(in-ns clojure.pprint)
(defn- use-method
"Installs a function as a new method of multimethod associated
with dispatch-value. "
[multifn dispatch-val func]
(. multifn addMethod dispatch-val func))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementations of specific dispatch table entries
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Dispatch for the basic data types when interpreted
;; as data (as opposed to code).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- map-ref-type
"Map ugly type names to something simpler"
[name]
(or (when-let [match (re-find #"^[^$]+\$[^$]+" name)]
(type-map match))
name))
(defmulti
simple-dispatch
"The pretty print dispatch function for simple data structure format."
{:added "1.2" :arglists [[object]]}
class)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Dispatch for the code table
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare pprint-simple-code-list)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something that looks like a simple def (sans metadata,
;;; since the reader wont give it to us now).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something that looks like a defn or defmacro
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format the params and body of a defn with a single arity
(defn- single-defn [alis has-doc-str?]
(if (seq alis)
(do
(if has-doc-str?
((formatter-out " ~_"))
((formatter-out " ~@_")))
((formatter-out "~{~w~^ ~_~}") alis))))
;;; Format the param and body sublists of a defn with multiple arities
(defn- multi-defn [alis has-doc-str?]
(if (seq alis)
((formatter-out " ~_~{~w~^ ~_~}") alis)))
1442 CHAPTER 11. CLJ/CLOJURE/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something with a binding form
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Format something that looks like "if"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The master definitions for formatting lists in code (that
;;; is, (fn args...) or special forms).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Take a map with symbols as keys and add versions with no namespace.
;;; That is, if ns/sym->val is in the map, add sym->val to the result.
(defn- two-forms [amap]
(into {}
(mapcat
identity
(for [x amap]
[x [(symbol (name (first x))) (second x)]]))))
(defmulti
code-dispatch
"The pretty print dispatch function for pretty printing Clojure code."
{:added "1.2" :arglists [[object]]}
class)
(set-pprint-dispatch simple-dispatch)
(with-pprint-dispatch code-dispatch
(pprint
(defn cl-format
"An implementation of a Common Lisp compatible format function"
[stream format-in & args]
(let [compiled-format
(if (string? format-in)
(compile-format format-in)
format-in)
navigator (init-navigator args)]
(execute-format stream compiled-format navigator)))))
(with-pprint-dispatch code-dispatch
(pprint
(defn cl-format
[stream format-in & args]
(let [compiled-format
(if (string? format-in)
(compile-format format-in)
format-in)
navigator (init-navigator args)]
(execute-format stream compiled-format navigator)))))
11.19. DISPATCH.CLJ 1447
(with-pprint-dispatch code-dispatch
(pprint
(defn- -write
([this x]
(condp = (class x)
String
(let [s0 (write-initial-lines this x)
s (.replaceFirst s0 "\\s+$" "")
white-space (.substring s0 (count s))
mode (getf :mode)]
(if (= mode :writing)
(dosync
(write-white-space this)
(.col_write this s)
(setf :trailing-white-space white-space))
(add-to-buffer this (make-buffer-blob s white-space))))
Integer
(let [c ^Character x]
(if (= (getf :mode) :writing)
(do
(write-white-space this)
(.col_write this x))
(if (= c (int \newline))
(write-initial-lines this "\n")
(add-to-buffer this
(make-buffer-blob (str (char c)) nil))))))))))
(with-pprint-dispatch code-dispatch
(pprint
(defn pprint-defn [writer alis]
(if (next alis)
(let [[defn-sym defn-name & stuff] alis
[doc-str stuff] (if (string? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])
[attr-map stuff] (if (map? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])]
(pprint-logical-block writer :prefix "(" :suffix ")"
(cl-format true "~w ~1I~@_~w" defn-sym defn-name)
(if doc-str (cl-format true " ~_~w" doc-str))
(if attr-map (cl-format true " ~_~w" attr-map))
;; Note: the multi-defn case will work OK for
;; malformed defns too
(cond
(vector? (first stuff))
(single-defn stuff (or doc-str attr-map))
:else (multi-defn stuff (or doc-str attr-map)))))
(pprint-simple-code-list writer alis)))))
1448 CHAPTER 11. CLJ/CLOJURE/
)
nil
11.20 pprintbase.clj
pprintbase.clj
\getchunk{Clojure Copyright}
;;; pprint_base.clj -- part of the pretty printer for Clojure
(in-ns clojure.pprint)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Variables that control the pretty printer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; *print-length*, *print-level* and *print-dup* are defined in
;;; clojure.core
;;; TODO: use *print-dup* here (or is it supplanted by other
;;; variables?)
;;; TODO: make dispatch items like "(let..." get counted in
;;; *print-length* constructs
(def ^:dynamic
^{:doc "Bind to true if you want write to use pretty printing",
:added "1.2"}
*print-pretty* true)
(def ^:dynamic
11.20. PPRINTBASE.CLJ 1449
^{:doc "Pretty printing will try to avoid anything going beyond this
column. Set it to nil to have pprint let the line be arbitrarily
long. This will ignore all non-mandatory newlines.",
:added "1.2"}
*print-right-margin* 72)
(def ^:dynamic
^{:doc "The column at which to enter miser style. Depending on the
dispatch table, miser style add newlines in more places to
try to keep lines short allowing for further levels of
nesting.",
:added "1.2"}
*print-miser-width* 40)
(def ^:dynamic
^{:doc "Dont print namespaces with symbols. This is particularly
useful when pretty printing the results of macro expansions"
:added "1.2"}
*print-suppress-namespaces* nil)
(def ^:dynamic
^{:doc "The base to use for printing integers and rationals."
:added "1.2"}
*print-base* 10)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal variables that keep track of where we are in the
;; structure
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for the write function
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare format-simple-number)
(defn- table-ize [t m]
(apply hash-map
(mapcat
#(when-let [v (get t (key %))] [(find-var v) (val %)]) m)))
(defn- pretty-writer?
"Return true iff x is a PrettyWriter"
[x] (and (instance? clojure.lang.IDeref x) (:pretty-writer @@x)))
(defn- make-pretty-writer
"Wrap base-writer in a PrettyWriter with the specified right-margin
and miser-width"
[base-writer right-margin miser-width]
(pretty-writer base-writer right-margin miser-width))
;;;TODO: if pretty print is not set, dont use pr but rather something
;;;that respects *print-base*, etc.
(defn write-out
"Write an object to *out* subject to the current bindings of the
printer control variables. Use the kw-args argument to override
individual variables for this call (and any recursive calls).
{:added "1.2"}
[object]
(let [length-reached (and
*current-length*
*print-length*
(>= *current-length* *print-length*))]
(if-not *print-pretty*
(pr object)
(if length-reached
(print "...")
(do
(if *current-length*
(set! *current-length* (inc *current-length*)))
(*print-pprint-dispatch* object))))
length-reached))
(defn write
"Write an object subject to the current bindings of the printer
control variables. Use the kw-args argument to override individual
variables for this call (and any recursive calls). Returns the
string result if :stream is nil or nil otherwise.
(defn pprint
"Pretty print object to the optional output writer. If the writer
is not provided, print the object to the currently bound value
of *out*."
{:added "1.2"}
([object] (pprint object *out*))
([object writer]
(with-pretty-writer writer
(binding [*print-pretty* true]
(binding-map
(if (or (not (= *print-base* 10))
*print-radix*)
{#pr pr-with-base}
{})
(write-out object)))
(if (not (= 0 (get-column *out*)))
(prn)))))
(defmacro pp
"A convenience macro that pretty prints the last thing output.
This is exactly equivalent to (pprint *1)."
{:added "1.2"}
[] (pprint *1))
1454 CHAPTER 11. CLJ/CLOJURE/
(defn set-pprint-dispatch
"Set the pretty print dispatch function to a function matching
(fn [obj] ...) where obj is the object to pretty print. That
function will be called with *out* set to a pretty printing
writer to which it should do its printing.
(defmacro with-pprint-dispatch
"Execute body with the pretty print dispatch function bound to
function."
{:added "1.2"}
[function & body]
(binding [*print-pprint-dispatch* ~function]
~@body))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Support for the functional interface to the pretty printer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- level-exceeded []
(and *print-level* (>= *current-level* *print-level*)))
(defmacro pprint-logical-block
"Execute the body as a pretty printing logical block with output to
*out* which must be a pretty printing writer. When used from pprint
or cl-format, this can be assumed.
11.20. PPRINTBASE.CLJ 1455
(defn pprint-newline
"Print a conditional newline to a pretty printing stream. kind
specifies if the newline is :linear, :miser, :fill, or :mandatory.
(defn pprint-indent
"Create an indent at this point in the pretty printing stream. This
defines how following lines are indented. relative-to can be either
:block or :current depending whether the indent should be computed
relative to the start of the logical block or the current column
position. n is an offset.
Colnum and colinc specify the target column and the increment to
move the target forward if the output is already past the original
target.
nil
11.21 prettywriter.clj
prettywriter.clj
\getchunk{Clojure Copyright}
;;; pretty_writer.clj -- part of the pretty printer for Clojure
(in-ns clojure.pprint)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Forward declarations
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare get-miser-width)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Macros to simplify dealing with types and classes. These are
;;; really utilities, but Im experimenting with them here.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; The data structures used by pretty-writer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; A newline
(deftype nl-t :type :logical-block :start-pos :end-pos)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Functions to write tokens in the output buffer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare emit-nl)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; emit-nl? method defs for each type of new line. This makes
;;; the decision about whether to print this type of new line.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(nil? maxcol)
(< (+ (get-column (getf :base)) (buffer-length tokens)) maxcol))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Various support functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
lb (:logical-block nl)
section
(seq
(take-while
#(let [nl-lb (:logical-block %)]
(not (and (nl-t? %)
(or (= nl-lb lb) (ancestor? nl-lb lb)))))
(next buffer)))]
section))
;;; Add a buffer token to the buffer and see if its time to start
;;; writing
(defn- add-to-buffer [^Writer this token]
; (prlabel a2b token)
(dosync
(setf :buffer (conj (getf :buffer) token))
11.21. PRETTYWRITER.CLJ 1463
;;; If there are newlines in the string, print the lines up until
;;; the last newline, making the appropriate adjustments. Return
;;; the remainder of the string
(defn- write-initial-lines
[^Writer this ^String s]
(let [lines (.split s "\n" -1)]
(if (= (count lines) 1)
s
(dosync
(let [^String prefix (:per-line-prefix
(first (getf :logical-blocks)))
^String l (first lines)]
(if (= :buffering (getf :mode))
(let [oldpos (getf :pos)
newpos (+ oldpos (count l))]
(setf :pos newpos)
(add-to-buffer this
(make-buffer-blob l nil oldpos newpos))
(write-buffered-output this))
(do
(write-white-space this)
(.write (getf :base) l)))
(.write (getf :base) (int \newline))
(doseq [^String l (next (butlast lines))]
(.write (getf :base) l)
(.write (getf :base) (pp-newline))
(if prefix
(.write (getf :base) prefix)))
(setf :buffering :writing)
(last lines))))))
1464 CHAPTER 11. CLJ/CLOJURE/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Initialize the pretty-writer instance
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(write
([x]
;; (prlabel write x (getf :mode))
(condp = (class x)
String
(let [^String s0 (write-initial-lines this x)
^String s (.replaceFirst s0 "\\s+$" "")
white-space (.substring s0 (count s))
mode (getf :mode)]
(dosync
(if (= mode :writing)
(do
(write-white-space this)
11.21. PRETTYWRITER.CLJ 1465
Integer
(p-write-char this x)
Long
(p-write-char this x))))
(flush []
(if (= (getf :mode) :buffering)
(dosync
(write-tokens this (getf :buffer) true)
(setf :buffer []))
(write-white-space this)))
(close []
(.flush this)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Methods for pretty-writer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- start-block
[^Writer this
^String prefix ^String per-line-prefix ^String suffix]
(dosync
(let [lb (struct logical-block (getf :logical-blocks) nil
(ref 0) (ref 0) (ref false) (ref false)
prefix per-line-prefix suffix)]
(setf :logical-blocks lb)
(if (= (getf :mode) :writing)
(do
(write-white-space this)
(when-let [cb (getf :logical-block-callback)] (cb :start))
(if prefix
(.write (getf :base) prefix))
(let [col (get-column (getf :base))]
(ref-set (:start-col lb) col)
(ref-set (:indent lb) col)))
(let [oldpos (getf :pos)
newpos (+ oldpos (if prefix (count prefix) 0))]
(setf :pos newpos)
(add-to-buffer this (make-start-block-t lb oldpos newpos)))))))
1466 CHAPTER 11. CLJ/CLOJURE/
-
11.22. PRINTTABLE.CLJ 1467
11.22 printtable.clj
printtable.clj
\getchunk{Clojure Copyright}
(in-ns clojure.pprint)
(defn print-table
"Alpha - subject to change.
Prints a collection of maps in a textual table. Prints table
headings ks, and then a line of output for each row, corresponding
to the keys in ks. If ks are not specified, use the keys of the
first item in rows."
{:added "1.3"}
([ks rows]
(when (seq rows)
(let [widths
(map
(fn [k]
(apply max (count (str k))
(map #(count (str (get % k))) rows)))
ks)
fmts (map #(str "%-" % "s") widths)
fmt-row (fn [row]
(apply str
(interpose " | "
(for [[col fmt]
(map vector (map #(get row %) ks) fmts)]
(format fmt (str col))))))
header (fmt-row (zipmap ks ks))
bar (apply str (repeat (count header) "="))]
(println bar)
(println header)
(println bar)
(doseq [row rows]
(println (fmt-row row)))
(println bar))))
([rows] (print-table (keys (first rows)) rows)))
11.23 utilities.clj
utilities.clj
1468 CHAPTER 11. CLJ/CLOJURE/
\getchunk{Clojure Copyright}
;;; utilities.clj -- part of the pretty printer for Clojure
(in-ns clojure.pprint)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Helper functions for digesting formats in the various
;;; phases of their lives.
;;; These functions are actually pretty general.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- rtrim [s c]
"Trim all instances of c from the end of sequence s"
(let [len (count s)]
(if (and (pos? len) (= (nth s (dec (count s))) c))
(loop [n (dec len)]
(cond
(neg? n) ""
(not (= (nth s n) c)) (subs s 0 (inc n))
true (recur (dec n))))
s)))
(defn- ltrim [s c]
"Trim all instances of c from the beginning of sequence s"
(let [len (count s)]
(if (and (pos? len) (= (nth s 0) c))
(loop [n 0]
(if (or (= n len) (not (= (nth s n) c)))
(subs s n)
(recur (inc n))))
s)))
11.24 pprint.clj
pprint.clj
\getchunk{Clojure Copyright}
;;; pprint.clj -- Pretty printer and Common Lisp compatible format
;;; function (cl-format) for Clojure
(ns
^{:author "Tom Faulhaber",
:doc "A Pretty Printer for Clojure
Out of the box, pprint supports a simple structured format for basic
data and a specialized format for Clojure source code. More advanced
formats, including formats that dont look like Clojure data at all
like XML and JSON, can be rendered by creating custom dispatch
functions.
(load "pprint/utilities")
(load "pprint/column_writer")
(load "pprint/pretty_writer")
(load "pprint/pprint_base")
11.24. PPRINT.CLJ 1471
(load "pprint/cl_format")
(load "pprint/dispatch")
(load "pprint/print_table")
nil
11.24.1 java.clj
java.clj
\getchunk{Clojure Copyright}
(in-ns clojure.reflect)
(extend-protocol TypeReference
clojure.lang.Symbol
(typename [s] (str/replace (str s) "<>" "[]"))
Class
;; neither .getName not .getSimpleName returns the right thing,
;; so best to delegate to Type
(typename
[c]
(typename (Type/getType c)))
Type
(typename
[t]
(-> (.getClassName t))))
(defn- typesym
"Given a typeref, create a legal Clojure symbol version of the
types name."
[t]
(-> (typename t)
(str/replace "[]" "<>")
(symbol)))
(defn- resource-name
"Given a typeref, return implied resource name. Used by Reflectors
such as ASM that need to find and read classbytes from files."
1472 CHAPTER 11. CLJ/CLOJURE/
[typeref]
(-> (typename typeref)
(str/replace "." "/")
(str ".class")))
(defn- access-flag
[[name flag & contexts]]
{:name name :flag flag :contexts (set (map keyword contexts))})
(defn- field-descriptor->class-symbol
"Convert a Java field descriptor to a Clojure class symbol. Field
descriptors are described in section 4.3.2 of the JVM spec, 2nd ed.:
http://java.sun.com/docs/books/jvms/second_edition/html/
ClassFile.doc.html#14152"
[^String d]
{:pre [(string? d)]}
(typesym (Type/getType d)))
(defn- internal-name->class-symbol
"Convert a Java internal name to a Clojure class symbol. Internal
names uses slashes instead of dots, e.g. java/lang/String. See
Section 4.2 of the JVM spec, 2nd ed.:
http://java.sun.com/docs/books/jvms/second_edition/html/
ClassFile.doc.html#14757"
[d]
{:pre [(string? d)]}
(typesym (Type/getObjectType d)))
(def ^{:doc "The Java access bitflags, along with their friendly
names and the kinds of objects to which they can apply."}
flag-descriptors
(vec
(map access-flag
[[:public 0x0001 :class :field :method]
[:private 0x002 :class :field :method]
[:protected 0x0004 :class :field :method]
[:static 0x0008 :field :method]
[:final 0x0010 :class :field :method]
;; :super is ancient history and is unfindable (?) by
;; reflection. skip it
#_[:super 0x0020 :class]
[:synchronized 0x0020 :method]
[:volatile 0x0040 :field]
[:bridge 0x0040 :method]
[:varargs 0x0080 :method]
[:transient 0x0080 :field]
[:native 0x0100 :method]
[:interface 0x0200 :class]
[:abstract 0x0400 :class :method]
11.24. PPRINT.CLJ 1473
(defn- parse-flags
"Convert reflection bitflags into a set of keywords."
[flags context]
(reduce
(fn [result fd]
(if (and (get (:contexts fd) context)
(not (zero? (bit-and flags (:flag fd)))))
(conj result (:name fd))
result))
#{}
flag-descriptors))
(defrecord Constructor
[name declaring-class parameter-types exception-types flags])
(defn- constructor->map
[^java.lang.reflect.Constructor constructor]
(Constructor.
(symbol (.getName constructor))
(typesym (.getDeclaringClass constructor))
(vec (map typesym (.getParameterTypes constructor)))
(vec (map typesym (.getExceptionTypes constructor)))
(parse-flags (.getModifiers constructor) :method)))
(defn- declared-constructors
"Return a set of the declared constructors of class as a Clojure map."
[^Class cls]
(set (map
constructor->map
(.getDeclaredConstructors cls))))
(defrecord Method
[name return-type declaring-class parameter-types
exception-types flags])
(defn- method->map
[^java.lang.reflect.Method method]
(Method.
(symbol (.getName method))
(typesym (.getReturnType method))
(typesym (.getDeclaringClass method))
(vec (map typesym (.getParameterTypes method)))
(vec (map typesym (.getExceptionTypes method)))
(parse-flags (.getModifiers method) :method)))
1474 CHAPTER 11. CLJ/CLOJURE/
(defn- declared-methods
"Return a set of the declared constructors of class as a Clojure map."
[^Class cls]
(set (map
method->map
(.getDeclaredMethods cls))))
(defrecord Field
[name type declaring-class flags])
(defn- field->map
[^java.lang.reflect.Field field]
(Field.
(symbol (.getName field))
(typesym (.getType field))
(typesym (.getDeclaringClass field))
(parse-flags (.getModifiers field) :field)))
(defn- declared-fields
"Return a set of the declared fields of class as a Clojure map."
[^Class cls]
(set (map
field->map
(.getDeclaredFields cls))))
(defn- parse-method-descriptor
[^String md]
{:parameter-types (vec (map typesym (Type/getArgumentTypes md)))
:return-type (typesym (Type/getReturnType md))})
(defprotocol ClassResolver
(^InputStream resolve-class [this name]
"Given a class name, return that typerefs class bytes
as an InputStream."))
(extend-protocol ClassResolver
clojure.lang.Fn
11.24. PPRINT.CLJ 1475
ClassLoader
(resolve-class [this typeref]
(.getResourceAsStream this (resource-name typeref))))
11.25 reflect.clj
reflect.clj
\getchunk{Clojure Copyright}
(ns ^{:author "Stuart Halloway"
:added "1.3"
:doc "Reflection on Host Types
Alpha - subject to change.
Key features:
(defprotocol Reflector
"Protocol for reflection implementers."
(do-reflect [reflector typeref]))
(defprotocol TypeReference
"A TypeReference can be unambiguously converted to a type name on
the host platform.
(declare default-reflector)
(defn type-reflect
"Alpha - subject to change.
Reflect on a typeref, returning a map with :bases, :flags, and
:members. In the discussion below, names are always Clojure symbols.
Options:
(defn reflect
"Alpha - subject to change.
Reflect on the type of obj (or obj itself if obj is a class).
Return value and options are the same as for type-reflect. "
11.26. REPL.CLJ 1479
{:added "1.3"}
[obj & options]
(apply type-reflect (if (class? obj) obj (class obj)) options))
(load "reflect/java")
11.26 repl.clj
repl.clj
\getchunk{Houser Copyright}
(ns
#^{:author
"Chris Houser, Christophe Grand, Stephen Gilardi, Michel Salim"
:doc "Utilities meant to be used interactively at the REPL"}
clojure.repl
(:import (java.io LineNumberReader InputStreamReader PushbackReader)
(clojure.lang RT Reflector)))
(defn find-doc
"Prints documentation for any var whose documentation or name
contains a match for re-string-or-pattern"
{:added "1.0"}
[re-string-or-pattern]
(let [re (re-pattern re-string-or-pattern)
ms (concat (mapcat
#(sort-by :name (map meta (vals (ns-interns %))))
(all-ns))
(map namespace-doc (all-ns))
(map special-doc (keys special-doc-map)))]
(doseq [m ms
:when (and (:doc m)
(or (re-find (re-matcher re (:doc m)))
(re-find (re-matcher re (str (:name m))))))]
(print-doc m))))
(defmacro doc
"Prints documentation for a var or special form given its name"
{:added "1.0"}
[name]
(if-let [special-name ({& fn catch try finally try} name)]
(#print-doc (#special-doc special-name))
(cond
(special-doc-map name) (#print-doc (#special-doc ~name))
(resolve name) (#print-doc (meta (var ~name)))
(find-ns name) (#print-doc (namespace-doc (find-ns ~name))))))
;; ----------------------------------------------------------------------
;; Examine Clojure functions (Vars, really)
(defn source-fn
1482 CHAPTER 11. CLJ/CLOJURE/
"Returns a string of the source code for the given symbol, if it can
find it. This requires that the symbol resolve to a Var defined in
a namespace for which the .clj is in the classpath. Returns nil if
it cant find the source. For most REPL usage, source is more
convenient.
(defmacro source
"Prints the source code for the given symbol, if it can find it.
This requires that the symbol resolve to a Var defined in a
namespace for which the .clj is in the classpath.
(defn apropos
"Given a regular expression or stringable thing, return a seq of
all definitions in all currently-loaded namespaces that match the
str-or-pattern."
[str-or-pattern]
(let [matches? (if (instance? java.util.regex.Pattern str-or-pattern)
#(re-find str-or-pattern (str %))
#(.contains (str %) (str str-or-pattern)))]
(mapcat (fn [ns]
(filter matches? (keys (ns-publics ns))))
(all-ns))))
(defn dir-fn
"Returns a sorted seq of symbols naming public vars in
a namespace"
[ns]
(sort (map first (ns-publics (the-ns ns)))))
(defmacro dir
"Prints a sorted directory of public vars in a namespace"
11.26. REPL.CLJ 1483
[nsname]
(doseq [v# (dir-fn ~nsname)]
(println v#)))
(defn demunge
"Given a string representation of a fn class,
as in a stack trace element, returns a readable version."
{:added "1.3"}
[fn-name]
(re-replace demunge-pattern fn-name demunge-map))
(defn root-cause
"Returns the initial cause of an exception or error by peeling off
all of its wrappers"
{:added "1.3"}
[^Throwable t]
(loop [cause t]
(if (and (instance? clojure.lang.Compiler$CompilerException cause)
(not= (.source
^clojure.lang.Compiler$CompilerException cause)
"NO_SOURCE_FILE"))
cause
(if-let [cause (.getCause cause)]
(recur cause)
cause))))
(defn stack-element-str
"Returns a (possibly unmunged) string representation of a
1484 CHAPTER 11. CLJ/CLOJURE/
StackTraceElement"
{:added "1.3"}
[^StackTraceElement el]
(let [file (.getFileName el)
clojure-fn? (and file (or (.endsWith file ".clj")
(= file "NO_SOURCE_FILE")))]
(str (if clojure-fn?
(demunge (.getClassName el))
(str (.getClassName el) "." (.getMethodName el)))
" (" (.getFileName el) ":" (.getLineNumber el) ")")))
(defn pst
"Prints a stack trace of the exception, to the depth requested. If
none supplied, uses the root cause of the most recent repl
exception (*e), and a depth of 12."
{:added "1.3"}
([] (pst 12))
([e-or-depth]
(if (instance? Throwable e-or-depth)
(pst e-or-depth 12)
(when-let [e *e]
(pst (root-cause e) e-or-depth))))
([^Throwable e depth]
(binding [*out* *err*]
(println (str (-> e class .getSimpleName) " " (.getMessage e)))
(let [st (.getStackTrace e)
cause (.getCause e)]
(doseq [el (take depth
(remove
#(#{"clojure.lang.RestFn" "clojure.lang.AFn"}
(.getClassName %))
st))]
(println (str \tab (stack-element-str el))))
(when cause
(println "Caused by:")
(pst cause (min depth
(+ 2 (- (count (.getStackTrace cause))
(count st))))))))))
;; ----------------------------------------------------------------------
;; Handle Ctrl-C keystrokes
(defn thread-stopper
"Returns a function that takes one arg and uses that as an
exception message to stop the given thread. Defaults to the
current thread"
([] (thread-stopper (Thread/currentThread)))
([thread] (fn [msg] (.stop thread (Error. msg)))))
(defn set-break-handler!
11.27. SET.CLJ 1485
"Register INT signal handler. After calling this, Ctrl-C will cause
the given function f to be called with a single argument, the signal.
Uses thread-stopper if no function given."
([] (set-break-handler! (thread-stopper)))
([f]
(sun.misc.Signal/handle
(sun.misc.Signal. "INT")
(proxy [sun.misc.SignalHandler] []
(handle [signal]
(f (str "-- caught signal " signal)))))))
11.27 set.clj
set.clj
\getchunk{Clojure Copyright}
(defn union
"Return a set that is the union of the input sets"
{:added "1.0"}
([] #{})
([s1] s1)
([s1 s2]
(if (< (count s1) (count s2))
(reduce conj s2 s1)
(reduce conj s1 s2)))
([s1 s2 & sets]
(let [bubbled-sets (bubble-max-key count (conj sets s2 s1))]
(reduce into (first bubbled-sets) (rest bubbled-sets)))))
(defn intersection
"Return a set that is the intersection of the input sets"
{:added "1.0"}
([s1] s1)
([s1 s2]
1486 CHAPTER 11. CLJ/CLOJURE/
(defn difference
"Return a set that is the first set without elements of the
remaining sets"
{:added "1.0"}
([s1] s1)
([s1 s2]
(if (< (count s1) (count s2))
(reduce (fn [result item]
(if (contains? s2 item)
(disj result item)
result))
s1 s1)
(reduce disj s1 s2)))
([s1 s2 & sets]
(reduce difference s1 (conj sets s2))))
(defn select
"Returns a set of the elements for which pred is true"
{:added "1.0"}
[pred xset]
(reduce (fn [s k] (if (pred k) s (disj s k)))
xset xset))
(defn project
"Returns a rel of the elements of xrel with only the keys in ks"
{:added "1.0"}
[xrel ks]
(set (map #(select-keys % ks) xrel)))
(defn rename-keys
"Returns the map with the keys in kmap renamed to the vals in kmap"
{:added "1.0"}
[map kmap]
(reduce
(fn [m [old new]]
(if (and (not= old new)
11.27. SET.CLJ 1487
(contains? m old))
(-> m (assoc new (get m old)) (dissoc old))
m))
map kmap))
(defn rename
"Returns a rel of the maps in xrel with the keys in kmap renamed
to the vals in kmap"
{:added "1.0"}
[xrel kmap]
(set (map #(rename-keys % kmap) xrel)))
(defn index
"Returns a map of the distinct values of ks in the xrel mapped to a
set of the maps in xrel with the corresponding values of ks."
{:added "1.0"}
[xrel ks]
(reduce
(fn [m x]
(let [ik (select-keys x ks)]
(assoc m ik (conj (get m ik #{}) x))))
{} xrel))
(defn map-invert
"Returns the map with the vals mapped to the keys."
{:added "1.0"}
[m] (reduce (fn [m [k v]] (assoc m v k)) {} m))
(defn join
"When passed 2 rels, returns the rel corresponding to the natural
join. When passed an additional keymap, joins on the corresponding
keys."
{:added "1.0"}
([xrel yrel] ;natural join
(if (and (seq xrel) (seq yrel))
(let [ks (intersection (set (keys (first xrel)))
(set (keys (first yrel))))
[r s] (if (<= (count xrel) (count yrel))
[xrel yrel]
[yrel xrel])
idx (index r ks)]
(reduce (fn [ret x]
(let [found (idx (select-keys x ks))]
(if found
(reduce #(conj %1 (merge %2 x)) ret found)
ret)))
#{} s))
#{}))
([xrel yrel km] ;arbitrary key mapping
(let [[r s k] (if (<= (count xrel) (count yrel))
1488 CHAPTER 11. CLJ/CLOJURE/
(defn subset?
"Is set1 a subset of set2?"
{:added "1.2",
:tag Boolean}
[set1 set2]
(and (<= (count set1) (count set2))
(every? #(contains? set2 %) set1)))
(defn superset?
"Is set1 a superset of set2?"
{:added "1.2",
:tag Boolean}
[set1 set2]
(and (>= (count set1) (count set2))
(every? #(contains? set1 %) set2)))
(comment
(refer set)
(def xs #{{:a 11 :b 1 :c 1 :d 4}
{:a 2 :b 12 :c 2 :d 6}
{:a 3 :b 3 :c 3 :d 8 :f 42}})
(def ys #{{:a 11 :b 11 :c 11 :e 5}
{:a 12 :b 11 :c 12 :e 3}
{:a 3 :b 3 :c 3 :e 7 }})
(join xs ys)
(join xs (rename ys {:b :yb :c :yc}) {:a :a})
(index ys [:b])
)
-
11.28. STACKTRACE.CLJ 1489
11.28 stacktrace.clj
stacktrace.clj
\getchunk{Clojure Copyright}
;; by Stuart Sierra
;; January 6, 2009
(ns ^{:doc "Print stack traces oriented towards Clojure, not Java."
:author "Stuart Sierra"}
clojure.stacktrace)
(defn root-cause
"Returns the last cause Throwable in a chain of Throwables."
{:added "1.1"}
[tr]
(if-let [cause (.getCause tr)]
(recur cause)
tr))
(defn print-trace-element
"Prints a Clojure-oriented view of one element in a stack trace."
{:added "1.1"}
[e]
(let [class (.getClassName e)
method (.getMethodName e)]
(let [match (re-matches #"^([A-Za-z0-9_.-]+)\$(\w+)__\d+$"
(str class))]
(if (and match (= "invoke" method))
(apply printf "%s/%s" (rest match))
(printf "%s.%s" class method))))
(printf " (%s:%d)" (or (.getFileName e) "") (.getLineNumber e)))
(defn print-throwable
"Prints the class and message of a Throwable."
{:added "1.1"}
[tr]
(printf "%s: %s" (.getName (class tr)) (.getMessage tr)))
(defn print-stack-trace
"Prints a Clojure-oriented stack trace of tr, a Throwable.
Prints a maximum of n stack frames (default: unlimited).
Does not print chained exceptions (causes)."
{:added "1.1"}
([tr] (print-stack-trace tr nil))
1490 CHAPTER 11. CLJ/CLOJURE/
([tr n]
(let [st (.getStackTrace tr)]
(print-throwable tr)
(newline)
(print " at ")
(print-trace-element (first st))
(newline)
(doseq [e (if (nil? n)
(rest st)
(take (dec n) (rest st)))]
(print " ")
(print-trace-element e)
(newline)))))
(defn print-cause-trace
"Like print-stack-trace but prints chained exceptions (causes)."
{:added "1.1"}
([tr] (print-cause-trace tr nil))
([tr n]
(print-stack-trace tr n)
(when-let [cause (.getCause tr)]
(print "Caused by: " )
(recur cause n))))
(defn e
"REPL utility. Prints a brief stack trace for the root cause of the
most recent exception."
{:added "1.1"}
[]
(print-stack-trace (root-cause *e) 8))
11.29 string.clj
string.clj
\getchunk{Clojure Copyright}
(ns your.namespace.here
(:require [clojure.string :as str]))
11.29. STRING.CLJ 1491
(defn- replace-by
[^CharSequence s re f]
(let [m (re-matcher re s)]
(let [buffer (StringBuffer. (.length s))]
(loop []
(if (.find m)
(do (.appendReplacement m buffer (f (re-groups m)))
(recur))
(do (.appendTail m buffer)
(.toString buffer)))))))
string / string
char / char
pattern / (string or function of match).
(defn- replace-first-by
[^CharSequence s ^Pattern re f]
(let [m (re-matcher re s)]
(let [buffer (StringBuffer. (.length s))]
(if (.find m)
(let [rep (f (re-groups m))]
(.appendReplacement m buffer rep)
(.appendTail m buffer)
(str buffer))))))
(defn- replace-first-char
[^CharSequence s ^Character match replace]
(let [s (.toString s)
i (.indexOf s (int match))]
(if (= -1 i)
s
(str (subs s 0 i) replace (subs s (inc i))))))
char / char
string / string
pattern / (string or function of match).
{:added "1.2"}
[^CharSequence s match replacement]
(let [s (.toString s)]
(cond
(instance? Character match)
(replace-first-char s match replacement)
(instance? CharSequence match)
(.replaceFirst s (Pattern/quote (.toString ^CharSequence match))
(.toString ^CharSequence replacement))
(instance? Pattern match)
(if (instance? CharSequence replacement)
(.replaceFirst (re-matcher ^Pattern match s)
(.toString ^CharSequence replacement))
(replace-first-by s match replacement))
:else (throw (IllegalArgumentException.
(str "Invalid match arg: " match))))))
(defn split
"Splits string on a regular expression. Optional argument limit is
the maximum number of splits. Not lazy. Returns vector of the splits."
{:added "1.2"}
([^CharSequence s ^Pattern re]
(LazilyPersistentVector/createOwning (.split re s)))
([ ^CharSequence s ^Pattern re limit]
(LazilyPersistentVector/createOwning (.split re s limit))))
(defn split-lines
"Splits s on \\n or \\r\\n."
{:added "1.2"}
[^CharSequence s]
(split s #"\r?\n"))
(defn blank?
"True if s is nil, empty, or contains only whitespace."
{:added "1.2"}
[^CharSequence s]
(if s
(loop [index (int 0)]
(if (= (.length s) index)
true
(if (Character/isWhitespace (.charAt s index))
(recur (inc index))
false)))
true))
-
1496 CHAPTER 11. CLJ/CLOJURE/
11.30 template.clj
template.clj
\getchunk{Clojure Copyright}
;; By Stuart Sierra
;; June 23, 2009
;; CHANGE LOG
;;
;; June 23, 2009: complete rewrite, eliminated _1,_2,... argument
;; syntax
;;
;; January 20, 2009: added "template?" and checks for valid template
;; expressions.
;;
;; December 15, 2008: first version
(defn apply-template
"For use in macros. argv is an argument list, as in defn. expr is
a quoted expression using the symbols in argv. values is a sequence
of values to be used for the arguments.
(defmacro do-template
"Repeatedly copies expr (in a do block) for each group of arguments
in values. values are automatically partitioned by the number of
arguments in argv, an argument vector as in defn.
11.31. JUNIT.CLJ 1497
11.31 junit.clj
junit.clj
\getchunk{Clojure Copyright}
;; by Jason Sankey
;; June 2009
;; DOCUMENTATION
;;
(use clojure.test)
(use clojure.test.junit)
(with-junit-output
(run-tests my.cool.library))
(defn indent
[]
(dotimes [n (* *depth* 4)] (print " ")))
(defn start-element
[tag pretty & [attrs]]
(if pretty (indent))
(print (str "<" tag))
(if (seq attrs)
(doseq [[key value] attrs]
(print (str " " (name key) "=\"" (escape-xml value) "\""))))
(print ">")
(if pretty (println))
(set! *depth* (inc *depth*)))
(defn element-content
[content]
(print (escape-xml content)))
(defn finish-element
[tag pretty]
(set! *depth* (dec *depth*))
(if pretty (indent))
(print (str "</" tag ">"))
(if pretty (println)))
(defn test-name
[vars]
(apply str (interpose "."
(reverse (map #(:name (meta %)) vars)))))
(defn package-class
[name]
(let [i (.lastIndexOf name ".")]
(if (< i 0)
[nil name]
[(.substring name 0 i) (.substring name (+ i 1))])))
(defn start-case
11.31. JUNIT.CLJ 1499
[name classname]
(start-element testcase true {:name name :classname classname}))
(defn finish-case
[]
(finish-element testcase true))
(defn suite-attrs
[package classname]
(let [attrs {:name classname}]
(if package
(assoc attrs :package package)
attrs)))
(defn start-suite
[name]
(let [[package classname] (package-class name)]
(start-element testsuite true (suite-attrs package classname))))
(defn finish-suite
[]
(finish-element testsuite true))
(defn message-el
[tag message expected-str actual-str]
(indent)
(start-element tag false (if message {:message message} {}))
(element-content
(let [[file line] (t/file-position 5)
detail (apply str (interpose
"\n"
[(str "expected: " expected-str)
(str " actual: " actual-str)
(str " at: " file ":" line)]))]
(if message (str message "\n" detail) detail)))
(finish-element tag false)
(println))
(defn failure-el
[message expected actual]
(message-el failure message (pr-str expected) (pr-str actual)))
(defn error-el
[message expected actual]
(message-el error
message
(pr-str expected)
(if (instance? Throwable actual)
(with-out-str
(stack/print-cause-trace actual
1500 CHAPTER 11. CLJ/CLOJURE/
t/*stack-trace-depth*))
(prn actual))))
(defmacro with-junit-output
"Execute body with modified test-is reporting functions that write
JUnit-compatible XML output."
{:added "1.1"}
11.32. TAP.CLJ 1501
[& body]
(binding [t/report junit-report
*var-context* (list)
*depth* 1]
(t/with-test-out
(println "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
(println "<testsuites>"))
(let [result# ~@body]
(t/with-test-out (println "</testsuites>"))
result#)))
11.32 tap.clj
tap.clj
\getchunk{Clojure Copyright}
;; by Stuart Sierra
;; March 31, 2009
;; DOCUMENTATION
;;
(ns ^{:doc "clojure.test extensions for the Test Anything Protocol (TAP)
(use clojure.test)
(use clojure.test.tap)
1502 CHAPTER 11. CLJ/CLOJURE/
(with-tap-output
(run-tests my.cool.library))"
:author "Stuart Sierra"}
clojure.test.tap
(:require [clojure.test :as t]
[clojure.stacktrace :as stack]))
(defn print-tap-plan
"Prints a TAP plan line like 1..n. n is the number of tests"
{:added "1.1"}
[n]
(println (str "1.." n)))
(defn print-tap-diagnostic
"Prints a TAP diagnostic line. data is a (possibly multi-line)
string."
{:added "1.1"}
[data]
(doseq [line (.split ^String data "\n")]
(println "#" line)))
(defn print-tap-pass
"Prints a TAP ok line. msg is a string, with no line breaks"
{:added "1.1"}
[msg]
(println "ok" msg))
(defn print-tap-fail
"Prints a TAP not ok line. msg is a string, with no line breaks"
{:added "1.1"}
[msg]
(println "not ok" msg))
(defmacro with-tap-output
"Execute body with modified test reporting functions that produce
TAP output"
{:added "1.1"}
[& body]
(binding [t/report tap-report]
~@body))
11.33 test.clj
test.clj
\getchunk{Clojure Copyright}
;; by Stuart Sierra
;; March 28, 2009
(ns
^{:author "Stuart Sierra, with contributions and suggestions by
Chas Emerick, Allen Rohner, and Stuart Halloway",
:doc "A unit testing framework.
ASSERTIONS
The core of the library is the \"is\" macro, which lets you make
assertions of any arbitrary expression:
(is (= 4 (+ 2 2)))
(is (instance? Integer 256))
(is (.startsWith \"abcde\" \"ab\"))
You can type an \"is\" expression directly at the REPL, which will
print a message if it fails.
FAIL in (:1)
expected: (= 5 (+ 2 2))
actual: (not (= 5 4))
false
The \"expected:\" line shows you the original expression, and the
\"actual:\" shows you what actually happened. In this case, it
shows that (+ 2 2) returned 4, which is not = to 5. Finally, the
\"false\" on the last line is the value returned from the
expression. The \"is\" macro always returns the result of the
inner expression.
DOCUMENTING TESTS
(testing \"Arithmetic\"
(testing \"with positive integers\"
(is (= 4 (+ 2 2)))
(is (= 7 (+ 3 4))))
(testing \"with negative integers\"
(is (= -4 (+ -2 -2)))
(is (= -1 (+ 3 -4)))))
Note that, unlike RSpec, the \"testing\" macro may only be used
INSIDE a \"deftest\" or \"with-test\" form (see below).
DEFINING TESTS
There are two ways to define tests. The \"with-test\" macro takes
a defn or def form as its first argument, followed by any number
of assertions. The tests will be stored as metadata on the
definition.
(with-test
(defn my-function [x y]
(+ x y))
(is (= 4 (my-function 2 2)))
(is (= 7 (my-function 3 4))))
As of Clojure SVN rev. 1221, this does not work with defmacro.
See http://code.google.com/p/clojure/issues/detail?id=51
The other way lets you define tests separately from the rest of
your code, even in a different namespace:
(deftest addition
(is (= 4 (+ 2 2)))
(is (= 7 (+ 3 4))))
(deftest subtraction
(is (= 1 (- 4 3)))
(is (= 3 (- 7 4))))
(deftest arithmetic
(addition)
(subtraction))
RUNNING TESTS
(defn test-ns-hook []
(arithmetic))
FIXTURES
Fixtures allow you to run code before and after tests, to set up
the context in which tests should be run.
You can extend the behavior of the \"is\" macro by defining new
methods for the \"assert-expr\" multimethod. These methods are
called during expansion of the \"is\" macro, so they should return
quoted forms to be evaluated.
(defonce ^:dynamic
^{:doc "True by default. If set to false, no test functions will
be created by deftest, set-test, or with-test. Use this to omit
tests when compiling or loading production code."
:added "1.1"}
*load-tests* true)
(def ^:dynamic
^{:doc "The maximum depth of stack traces to print when an Exception
is thrown during a test. Defaults to nil, which means print the
complete stack trace."
:added "1.1"}
*stack-trace-depth* nil)
(defmacro with-test-out
"Runs body with *out* bound to the value of *test-out*."
{:added "1.1"}
[& body]
(binding [*out* *test-out*]
~@body))
(defn file-position
"Returns a vector [filename line-number] for the nth call up the
stack.
(defn testing-vars-str
"Returns a string representation of the current test. Renders names
in *testing-vars* as a list, then the source file and line of
current assertion."
{:added "1.1"}
[m]
(let [{:keys [file line]} m]
(str
;; Uncomment to include namespace in failure report:
;;(ns-name (:ns (meta (first *testing-vars*)))) "/ "
(reverse (map #(:name (meta %)) *testing-vars*))
" (" file ":" line ")")))
(defn testing-contexts-str
"Returns a string representation of the current test context. Joins
strings in *testing-contexts* with spaces."
{:added "1.1"}
1510 CHAPTER 11. CLJ/CLOJURE/
[]
(apply str (interpose " " (reverse *testing-contexts*))))
(defn inc-report-counter
"Increments the named counter in *report-counters*, a ref to a map.
Does nothing if *report-counters* is nil."
{:added "1.1"}
[name]
(when *report-counters*
(dosync (commute *report-counters* assoc name
(inc (or (*report-counters* name) 0))))))
(defmulti
^{:doc "Generic reporting function, may be overridden to plug in
different report formats (e.g., TAP, JUnit). Assertions such as
is call report to indicate results. The argument given to
report will be a map with a :type key. See the documentation at
the top of test_is.clj for more information on the types of
arguments for report."
:dynamic true
:added "1.1"}
report :type)
(defn- file-and-line
[exception depth]
(let [^StackTraceElement s (nth (.getStackTrace exception) depth)]
{:file (.getFileName s) :line (.getLineNumber s)}))
(defn do-report
"Add file and line information to a test result and call report.
If you are writing a custom assert-expr method, call this function
to pass test results to report."
{:added "1.2"}
[m]
(report
(case
(:type m)
:fail (merge (file-and-line (new java.lang.Throwable) 1) m)
:error (merge (file-and-line (:actual m) 0) m)
m)))
(with-test-out
(inc-report-counter :fail)
(println "\nFAIL in" (testing-vars-str m))
(when (seq *testing-contexts*) (println (testing-contexts-str)))
(when-let [message (:message m)] (println message))
(println "expected:" (pr-str (:expected m)))
(println " actual:" (pr-str (:actual m)))))
(defn get-possibly-unbound-var
"Like var-get but returns nil if the var is unbound."
{:added "1.1"}
[v]
(try (var-get v)
(catch IllegalStateException e
nil)))
(defn function?
"Returns true if argument is a function or a symbol that resolves to
1512 CHAPTER 11. CLJ/CLOJURE/
(defn assert-predicate
"Returns generic assertion code for any functional predicate. The
expected argument to report will contains the original form, the
actual argument will contain the form with all its sub-forms
evaluated. If the predicate returns false, the actual form will
be wrapped in (not...)."
{:added "1.1"}
[msg form]
(let [args (rest form)
pred (first form)]
(let [values# (list ~@args)
result# (apply ~pred values#)]
(if result#
(do-report {:type :pass, :message ~msg,
:expected ~form, :actual (cons ~pred values#)})
(do-report {:type :fail, :message ~msg,
:expected ~form,
:actual (list ~not (cons ~pred values#))}))
result#)))
(defn assert-any
"Returns generic assertion code for any test, including macros, Java
method calls, or isolated symbols."
{:added "1.1"}
[msg form]
(let [value# ~form]
(if value#
(do-report {:type :pass, :message ~msg,
:expected ~form, :actual value#})
(do-report {:type :fail, :message ~msg,
:expected ~form, :actual value#}))
value#))
;; You dont call these, but you can add methods to extend the is
;; macro. These define different kinds of tests, based on the first
;; symbol in the test expression.
11.33. TEST.CLJ 1513
(defmulti assert-expr
(fn [msg form]
(cond
(nil? form) :always-fail
(seq? form) (first form)
:else :default)))
(defmacro try-expr
"Used by the is macro to catch unexpected exceptions.
You dont call this."
{:added "1.1"}
[msg form]
(try ~(assert-expr msg form)
(catch Throwable t#
(do-report {:type :error, :message ~msg,
:expected ~form, :actual t#}))))
(defmacro is
"Generic assertion macro. form is any predicate test.
msg is an optional message to attach to the assertion.
Special forms:
(defmacro are
"Checks multiple assertions with a template expression.
11.33. TEST.CLJ 1515
Example: (are [x y] (= x y)
2 (+ 1 1)
4 (* 2 2))
Expands to:
(do (is (= 2 (+ 1 1)))
(is (= 4 (* 2 2))))
(defmacro testing
"Adds a new string to the list of testing contexts. May be nested,
but must occur inside a test function (deftest)."
{:added "1.1"}
[string & body]
(binding [*testing-contexts* (conj *testing-contexts* ~string)]
~@body))
(defmacro with-test
"Takes any definition form (that returns a Var) as the first argument.
Remaining body goes in the :test metadata function for that Var.
(defmacro deftest
"Defines a test function with no arguments. Test functions may call
other tests, so tests may be composed. If you compose tests, you
should also define a function named test-ns-hook; run-tests will
call test-ns-hook instead of testing all vars.
Note: Actually, the test body goes in the :test metadata on the var,
and the real function (the value of the var) calls test-var on
itself.
1516 CHAPTER 11. CLJ/CLOJURE/
(defmacro deftest-
"Like deftest but creates a private var."
{:added "1.1"}
[name & body]
(when *load-tests*
(def ~(vary-meta name assoc :test (fn [] ~@body) :private true)
(fn [] (test-var (var ~name))))))
(defmacro set-test
"Experimental.
Sets :test metadata of the named var to a fn with the given body.
The var must already exist. Does not modify the value of the var.
(defn- add-ns-meta
"Adds elements in coll to the current namespace metadata as the
value of key."
{:added "1.1"}
[key coll]
(alter-meta! *ns* assoc key coll))
(defmulti use-fixtures
"Wrap test runs in a fixture function to perform setup and
teardown. Using a fixture-type of :each wraps every test
individually, while:once wraps the whole run in a single function."
{:added "1.1"}
(fn [fixture-type & args] fixture-type))
(defn- default-fixture
"The default, empty, fixture function. Just calls its argument."
{:added "1.1"}
[f]
(f))
(defn compose-fixtures
"Composes two fixture functions, creating a new fixture function
that combines their behavior."
{:added "1.1"}
[f1 f2]
(fn [g] (f1 (fn [] (f2 g)))))
(defn join-fixtures
"Composes a collection of fixtures, in order. Always returns a valid
fixture function, even if the collection is empty."
{:added "1.1"}
[fixtures]
(reduce compose-fixtures default-fixture fixtures))
(defn test-var
"If v has a function in its :test metadata, calls that function,
with *testing-vars* bound to (conj *testing-vars* v)."
{:dynamic true, :added "1.1"}
[v]
(when-let [t (:test (meta v))]
(binding [*testing-vars* (conj *testing-vars* v)]
(do-report {:type :begin-test-var, :var v})
(inc-report-counter :test)
(try (t)
(catch Throwable e
(do-report {:type :error,
:message "Uncaught exception, not in assertion."
:expected nil, :actual e})))
(do-report {:type :end-test-var, :var v}))))
(defn test-all-vars
"Calls test-var on every var interned in the namespace, with fixtures."
{:added "1.1"}
[ns]
(let [once-fixture-fn (join-fixtures (::once-fixtures (meta ns)))
each-fixture-fn (join-fixtures (::each-fixtures (meta ns)))]
(once-fixture-fn
(fn []
1518 CHAPTER 11. CLJ/CLOJURE/
(defn test-ns
"If the namespace defines a function named test-ns-hook, calls that.
Otherwise, calls test-all-vars on the namespace. ns is a
namespace object or a symbol.
(defn run-tests
"Runs all tests in the given namespaces; prints results.
Defaults to current namespace if none given. Returns a map
summarizing test results."
{:added "1.1"}
([] (run-tests *ns*))
([& namespaces]
(let [summary (assoc (apply merge-with + (map test-ns namespaces))
:type :summary)]
(do-report summary)
summary)))
(defn run-all-tests
"Runs all tests in all namespaces; prints results.
Optional argument is a regular expression; only namespaces with
names matching the regular expression (with re-matches) will be
tested."
{:added "1.1"}
([] (apply run-tests (all-ns)))
([re] (apply run-tests
11.34. VERSION.PROPERTIES 1519
(defn successful?
"Returns true if the given test summary indicates all tests
were successful, false otherwise."
{:added "1.1"}
[summary]
(and (zero? (:fail summary 0))
(zero? (:error summary 0))))
11.34 version.properties
version.properties
clojure.version.major=1
clojure.version.minor=3
clojure.version.incremental=0
clojure.version.qualifier=master
clojure.version.interim=interim
11.35 walk.clj
walk.clj
\getchunk{Clojure Copyright}
;; by Stuart Sierra
;; December 15, 2008
;; CHANGE LOG:
;;
;; * December 15, 2008: replaced walk with prewalk & postwalk
;;
;; * December 9, 2008: first version
(ns
^{:author "Stuart Sierra",
1520 CHAPTER 11. CLJ/CLOJURE/
:doc "This file defines a generic tree walker for Clojure data
structures. It takes any data structure (list, vector, map, set,
seq), calls a function on every element, and uses the return value
of the function in place of the original. This makes it fairly
easy to write recursive search-and-replace functions, as shown in
the examples.
(defn walk
"Traverses form, an arbitrary data structure. inner and outer are
functions. Applies inner to each element of form, building up a
data structure of the same type, then applies outer to the result.
Recognizes all Clojure data structures except sorted-map-by.
Consumes seqs as with doall."
{:added "1.1"}
[inner outer form]
(cond
(list? form) (outer (apply list (map inner form)))
(seq? form) (outer (doall (map inner form)))
(vector? form) (outer (vec (map inner form)))
(map? form) (outer (into (if (sorted? form) (sorted-map) {})
(map inner form)))
(set? form) (outer (into (if (sorted? form) (sorted-set) #{})
(map inner form)))
:else (outer form)))
(defn postwalk
"Performs a depth-first, post-order traversal of form. Calls f on
each sub-form, uses fs return value in place of the original.
Recognizes all Clojure data structures except sorted-map-by.
Consumes seqs as with doall."
{:added "1.1"}
[f form]
(walk (partial postwalk f) f form))
(defn prewalk
"Like postwalk, but does pre-order traversal."
{:added "1.1"}
[f form]
(walk (partial prewalk f) identity (f form)))
(defn postwalk-demo
"Demonstrates the behavior of postwalk by printing each form as it is
walked. Returns form."
{:added "1.1"}
[form]
(postwalk (fn [x] (print "Walked: ") (prn x) x) form))
(defn prewalk-demo
"Demonstrates the behavior of prewalk by printing each form as it is
walked. Returns form."
{:added "1.1"}
[form]
(prewalk (fn [x] (print "Walked: ") (prn x) x) form))
(defn keywordize-keys
"Recursively transforms all map keys from strings to keywords."
{:added "1.1"}
[m]
(let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
;; only apply to maps
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn stringify-keys
"Recursively transforms all map keys from keywords to strings."
{:added "1.1"}
[m]
(let [f (fn [[k v]] (if (keyword? k) [(name k) v] [k v]))]
;; only apply to maps
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn prewalk-replace
"Recursively transforms form by replacing keys in smap with their
values. Like clojure/replace but works on any data structure. Does
replacement at the root of the tree first."
{:added "1.1"}
[smap form]
(prewalk (fn [x] (if (contains? smap x) (smap x) x)) form))
(defn postwalk-replace
"Recursively transforms form by replacing keys in smap with their
values. Like clojure/replace but works on any data structure. Does
replacement at the leaves of the tree first."
1522 CHAPTER 11. CLJ/CLOJURE/
{:added "1.1"}
[smap form]
(postwalk (fn [x] (if (contains? smap x) (smap x) x)) form))
(defn macroexpand-all
"Recursively performs all possible macroexpansions in form."
{:added "1.1"}
[form]
(prewalk (fn [x] (if (seq? x) (macroexpand x) x)) form))
11.36 xml.clj
xml.clj
\getchunk{Clojure Copyright}
(def content-handler
(let [push-content
(fn [e c] (assoc e :content (conj (or (:content e) []) c)))
push-chars
(fn []
(when (and (= *state* :chars)
(some (complement
#(Character/isWhitespace (char %)))
(str *sb*)))
(set! *current*
(push-content *current* (str *sb*)))))]
11.36. XML.CLJ 1523
(new clojure.lang.XMLHandler
(proxy [ContentHandler] []
(startElement [uri local-name q-name ^Attributes atts]
(let [attrs (fn [ret i]
(if (neg? i)
ret
(recur
(assoc ret
(clojure.lang.Keyword/intern
(symbol (.getQName atts i)))
(.getValue atts (int i)))
(dec i))))
e (struct element
(. clojure.lang.Keyword
(intern (symbol q-name)))
(when (pos? (.getLength atts))
(attrs {} (dec (.getLength atts)))))]
(push-chars)
(set! *stack* (conj *stack* *current*))
(set! *current* e)
(set! *state* :element))
nil)
(endElement [uri local-name q-name]
(push-chars)
(set! *current* (push-content (peek *stack*) *current*))
(set! *stack* (pop *stack*))
(set! *state* :between)
nil)
(characters [^chars ch start length]
(when-not (= *state* :chars)
(set! *sb* (new StringBuilder)))
(let [^StringBuilder sb *sb*]
(.append sb ch (int start) (int length))
(set! *state* :chars))
nil)
(setDocumentLocator [locator])
(startDocument [])
(endDocument [])
(startPrefixMapping [prefix uri])
(endPrefixMapping [prefix])
(ignorableWhitespace [ch start length])
(processingInstruction [target data])
(skippedEntity [name])
))))
(defn parse
"Parses and loads the source s, which can be a File, InputStream or
1524 CHAPTER 11. CLJ/CLOJURE/
;(load-file "/Users/rich/dev/clojure/src/xml.clj")
;(def x (xml/parse "http://arstechnica.com/journals.rssx"))
11.37 zip.clj
zip.clj
\getchunk{Clojure Copyright}
11.37. ZIP.CLJ 1525
(defn zipper
"Creates a new zipper structure.
(defn seq-zip
"Returns a zipper for nested sequences, given a root sequence"
{:added "1.0"}
[root]
(zipper seq?
identity
(fn [node children] (with-meta children (meta node)))
root))
(defn vector-zip
"Returns a zipper for nested vectors, given a root vector"
{:added "1.0"}
[root]
(zipper vector?
seq
(fn [node children] (with-meta (vec children) (meta node)))
root))
(defn xml-zip
"Returns a zipper for xml elements (as from xml/parse),
given a root element"
{:added "1.0"}
1526 CHAPTER 11. CLJ/CLOJURE/
[root]
(zipper (complement string?)
(comp seq :content)
(fn [node children]
(assoc node :content
(and children (apply vector children))))
root))
(defn node
"Returns the node at loc"
{:added "1.0"}
[loc] (loc 0))
(defn branch?
"Returns true if the node at loc is a branch"
{:added "1.0"}
[loc]
((:zip/branch? (meta loc)) (node loc)))
(defn children
"Returns a seq of the children of node at loc, which must be a branch"
{:added "1.0"}
[loc]
(if (branch? loc)
((:zip/children (meta loc)) (node loc))
(throw (Exception. "called children on a leaf node"))))
(defn make-node
"Returns a new branch node, given an existing node and new
children. The loc is only used to supply the constructor."
{:added "1.0"}
[loc node children]
((:zip/make-node (meta loc)) node children))
(defn path
"Returns a seq of nodes leading to this loc"
{:added "1.0"}
[loc]
(:pnodes (loc 1)))
(defn lefts
"Returns a seq of the left siblings of this loc"
{:added "1.0"}
[loc]
(seq (:l (loc 1))))
(defn rights
"Returns a seq of the right siblings of this loc"
{:added "1.0"}
[loc]
11.37. ZIP.CLJ 1527
(defn down
"Returns the loc of the leftmost child of the node at this loc, or
nil if no children"
{:added "1.0"}
[loc]
(when (branch? loc)
(let [[node path] loc
[c & cnext :as cs] (children loc)]
(when cs
(with-meta [c {:l []
:pnodes
(if path (conj (:pnodes path) node) [node])
:ppath path
:r cnext}] (meta loc))))))
(defn up
"Returns the loc of the parent of the node at this loc, or nil if at
the top"
{:added "1.0"}
[loc]
(let [[node {l :l, ppath :ppath, pnodes :pnodes r
:r, changed? :changed?, :as path}] loc]
(when pnodes
(let [pnode (peek pnodes)]
(with-meta (if changed?
[(make-node loc pnode (concat l (cons node r)))
(and ppath (assoc ppath :changed? true))]
[pnode ppath])
(meta loc))))))
(defn root
"zips all the way up and returns the root node, reflecting any
changes."
{:added "1.0"}
[loc]
(if (= :end (loc 1))
(node loc)
(let [p (up loc)]
(if p
(recur p)
(node loc)))))
(defn right
"Returns the loc of the right sibling of the node at this loc, or nil"
{:added "1.0"}
[loc]
(let [[node {l :l [r & rnext :as rs] :r :as path}] loc]
1528 CHAPTER 11. CLJ/CLOJURE/
(defn rightmost
"Returns the loc of the rightmost sibling of the node at this loc,
or self"
{:added "1.0"}
[loc]
(let [[node {l :l r :r :as path}] loc]
(if (and path r)
(with-meta [(last r)
(assoc path :l (apply conj l node (butlast r)) :r nil)]
(meta loc))
loc)))
(defn left
"Returns the loc of the left sibling of the node at this loc, or nil"
{:added "1.0"}
[loc]
(let [[node {l :l r :r :as path}] loc]
(when (and path (seq l))
(with-meta [(peek l) (assoc path :l (pop l) :r (cons node r))]
(meta loc)))))
(defn leftmost
"Returns the loc of the leftmost sibling of the node at this loc,
or self"
{:added "1.0"}
[loc]
(let [[node {l :l r :r :as path}] loc]
(if (and path (seq l))
(with-meta [(first l)
(assoc path :l [] :r (concat (rest l) [node] r))] (meta loc))
loc)))
(defn insert-left
"Inserts the item as the left sibling of the node at this loc,
without moving"
{:added "1.0"}
[loc item]
(let [[node {l :l :as path}] loc]
(if (nil? path)
(throw (new Exception "Insert at top"))
(with-meta [node (assoc path :l (conj l item) :changed? true)]
(meta loc)))))
(defn insert-right
"Inserts the item as the right sibling of the node at this loc,
without moving"
11.37. ZIP.CLJ 1529
{:added "1.0"}
[loc item]
(let [[node {r :r :as path}] loc]
(if (nil? path)
(throw (new Exception "Insert at top"))
(with-meta [node (assoc path :r (cons item r) :changed? true)]
(meta loc)))))
(defn replace
"Replaces the node at this loc, without moving"
{:added "1.0"}
[loc node]
(let [[_ path] loc]
(with-meta [node (assoc path :changed? true)] (meta loc))))
(defn edit
"Replaces the node at this loc with the value of (f node args)"
{:added "1.0"}
[loc f & args]
(replace loc (apply f (node loc) args)))
(defn insert-child
"Inserts the item as the leftmost child of the node at this loc,
without moving"
{:added "1.0"}
[loc item]
(replace loc
(make-node loc (node loc) (cons item (children loc)))))
(defn append-child
"Inserts the item as the rightmost child of the node at this loc,
without moving"
{:added "1.0"}
[loc item]
(replace loc
(make-node loc (node loc) (concat (children loc) [item]))))
(defn next
"Moves to the next loc in the hierarchy, depth-first. When reaching
the end, returns a distinguished loc detectable via end?. If already
at the end, stays there."
{:added "1.0"}
[loc]
(if (= :end (loc 1))
loc
(or
(and (branch? loc) (down loc))
(right loc)
(loop [p loc]
(if (up p)
1530 CHAPTER 11. CLJ/CLOJURE/
(defn prev
"Moves to the previous loc in the hierarchy, depth-first. If already
at the root, returns nil."
{:added "1.0"}
[loc]
(if-let [lloc (left loc)]
(loop [loc lloc]
(if-let [child (and (branch? loc) (down loc))]
(recur (rightmost child))
loc))
(up loc)))
(defn end?
"Returns true if loc represents the end of a depth-first walk"
{:added "1.0"}
[loc]
(= :end (loc 1)))
(defn remove
"Removes the node at loc, returning the loc that would have preceded
it in a depth-first walk."
{:added "1.0"}
[loc]
(let [[node {l :l, ppath :ppath, pnodes
:pnodes, rs :r, :as path}] loc]
(if (nil? path)
(throw (new Exception "Remove at top"))
(if (pos? (count l))
(loop [loc (with-meta [(peek l)
(assoc path :l (pop l) :changed? true)]
(meta loc))]
(if-let [child (and (branch? loc) (down loc))]
(recur (rightmost child))
loc))
(with-meta [(make-node loc (peek pnodes) rs)
(and ppath (assoc ppath :changed? true))]
(meta loc))))))
(comment
(load-file "/Users/rich/dev/clojure/src/zip.clj")
(refer zip)
(def data [[a * b] + [c * d]])
(def dz (vector-zip data))
(end? (-> dz next next next next next next next next next remove next))
11.38 pom-template.xml
pom-template.xml
<version>@clojure-version@</version>
<url>http://clojure.org/</url>
<description>
Clojure core environment and runtime library.
</description>
<licenses>
<license>
<name>Eclipse Public License 1.0</name>
<url>http://opensource.org/licenses/eclipse-1.0.php</url>
<distribution>repo</distribution>
</license>
</licenses>
</project>
-
Chapter 12
test/clojure
12.1 test/testclojure.clj
test/testclojure.clj
\getchunk{Clojure Copyright}
;; clojure.test-clojure
;;
;; Tests for the facilities provided by Clojure
;;
;; scgilardi (gmail)
;; Created 22 October 2008
(ns clojure.test-clojure
(:require [clojure.test :as t])
(:gen-class))
(def test-names
[:reader
:printer
:compilation
:evaluation
:special
:macros
:metadata
:ns-libs
:logic
:predicates
:control
:data-structures
:numbers
1533
1534 CHAPTER 12. TEST/CLOJURE
:sequences
:for
:multimethods
:other-functions
:vars
:refs
:agents
:atoms
:parallel
:java-interop
:test
:test-fixtures
;; libraries
:clojure-set
:clojure-xml
:clojure-zip
:protocols
:genclass
:main
:vectors
:annotations
:pprint
:serialization
:rt
:repl
:java.io
:string
:java.javadoc
:java.shell
:transients
:def
:keywords
:data
:reflect
:errors
])
(def test-namespaces
(map #(symbol (str "clojure.test-clojure." (name %)))
test-names))
(defn run
"Runs all defined tests"
[]
(println "Loading tests...")
(apply require :reload-all test-namespaces)
(apply t/run-tests test-namespaces))
(defn run-ant
"Runs all defined tests, prints report to *err*, throw if
12.2. TEST/TESTHELPER.CLJ 1535
(defn -main
"Run all defined tests from the command line"
[& args]
(run)
(System/exit 0))
12.2 test/testhelper.clj
test/testhelper.clj
\getchunk{Clojure Copyright}
;; clojure.test-helper
;;
;; Utility functions shared by various tests in the Clojure
;; test suite
;;
;; tomfaulhaber (gmail)
;; Created 04 November 2010
(ns clojure.test-helper
(:use clojure.test))
(defn temp-ns
"Create and return a temporary ns, using clojure.core + uses"
[& uses]
(binding [*ns* *ns*]
(in-ns (gensym))
(apply clojure.core/use clojure.core uses)
*ns*))
(defn causes
[^Throwable throwable]
(loop [causes []
t throwable]
(if t (recur (conj causes t) (.getCause t)) causes)))
(defn get-field
"Access to private or protected field. field-name is a symbol or
keyword."
([klass field-name]
(get-field klass field-name nil))
([klass field-name inst]
(-> klass (.getDeclaredField (name field-name))
(doto (.setAccessible true))
(.get inst))))
12.3. TEST/AGENTS.CLJ 1537
(defn set-var-roots
[maplike]
(doseq [[var val] maplike]
(alter-var-root var (fn [_] val))))
(defn with-var-roots*
"Temporarily set var roots, run block, then put original roots back."
[root-map f & args]
(let [originals (doall (map (fn [[var _]] [var @var]) root-map))]
(set-var-roots root-map)
(try
(apply f args)
(finally
(set-var-roots originals)))))
(defmacro with-var-roots
[root-map & body]
(with-var-roots* ~root-map (fn [] ~@body)))
(defn exception
"Use this function to ensure that execution of a program doesnt
reach certain point."
[]
(throw (new Exception "Exception which should never occur")))
12.3 test/agents.clj
test/agents.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.agents
(:use clojure.test)
(:import [java.util.concurrent CountDownLatch TimeUnit]))
(deftest handle-all-throwables-during-agent-actions
;; Bug fixed in r1198;
;; previously hung Clojure or didnt report agent errors
;; after OutOfMemoryError, yet wouldnt execute new actions.
(let [agt (agent nil)]
(send agt
(fn [state] (throw (Throwable. "just testing Throwables"))))
(try
1538 CHAPTER 12. TEST/CLOJURE
;; Let the action finish; eat the "agent has errors" error
;; that bubbles up
(await-for 100 agt)
(catch RuntimeException _))
(is (instance? Throwable (first (agent-errors agt))))
(is (= 1 (count (agent-errors agt))))
(deftest default-modes
(is (= :fail (error-mode (agent nil))))
(is (= :continue (error-mode (agent nil :error-handler println)))))
(deftest continue-handler
(let [err (atom nil)
agt (agent 0 :error-mode :continue
:error-handler #(reset! err %&))]
(send agt /)
(is (true? (await-for 100 agt)))
(is (= 0 @agt))
(is (nil? (agent-error agt)))
(is (= agt (first @err)))
(is (true? (instance? ArithmeticException (second @err))))))
(deftest fail-handler
(let [err (atom nil)
agt (agent 0 :error-mode :fail :error-handler #(reset! err %&))]
(send agt /)
(Thread/sleep 100)
(is (true? (instance? ArithmeticException (agent-error agt))))
(is (= 0 @agt))
(is (= agt (first @err)))
(is (true? (instance? ArithmeticException (second @err))))
(is (thrown? RuntimeException (send agt inc)))))
(deftest can-send-from-handler-before-popping-action-that-caused-error
(let [latch (CountDownLatch. 1)
target-agent (agent :before-error)
handler (fn [agt err]
(send target-agent
(fn [_] (.countDown latch))))
failing-agent (agent nil :error-handler handler)]
(send failing-agent (fn [_] (throw (RuntimeException.))))
(is (.await latch 10 TimeUnit/SECONDS))))
12.3. TEST/AGENTS.CLJ 1539
(deftest
can-send-to-self-from-handler-before-popping-action-that-caused-error
(let [latch (CountDownLatch. 1)
handler (fn [agt err]
(send *agent*
(fn [_] (.countDown latch))))
failing-agent (agent nil :error-handler handler)]
(send failing-agent (fn [_] (throw (RuntimeException.))))
(is (.await latch 10 TimeUnit/SECONDS))))
(deftest restart-no-clear
(let [p (promise)
agt (agent 1 :error-mode :fail)]
(send agt (fn [v] @p))
(send agt /)
(send agt inc)
(send agt inc)
(deliver p 0)
(Thread/sleep 100)
(is (= 0 @agt))
(is (= ArithmeticException (class (agent-error agt))))
(restart-agent agt 10)
(is (true? (await-for 100 agt)))
(is (= 12 @agt))
(is (nil? (agent-error agt)))))
(deftest restart-clear
(let [p (promise)
agt (agent 1 :error-mode :fail)]
(send agt (fn [v] @p))
(send agt /)
(send agt inc)
(send agt inc)
(deliver p 0)
(Thread/sleep 100)
(is (= 0 @agt))
(is (= ArithmeticException (class (agent-error agt))))
(restart-agent agt 10 :clear-actions true)
(is (true? (await-for 100 agt)))
(is (= 10 @agt))
(is (nil? (agent-error agt)))
(send agt inc)
(is (true? (await-for 100 agt)))
(is (= 11 @agt))
(is (nil? (agent-error agt)))))
(deftest invalid-restart
(let [p (promise)
agt (agent 2 :error-mode :fail :validator even?)]
(is (thrown? RuntimeException (restart-agent agt 4)))
1540 CHAPTER 12. TEST/CLOJURE
(deftest earmuff-agent-bound
(let [a (agent 1)]
(send a (fn [_] *agent*))
(await a)
(is (= a @a))))
(deftest thread-conveyance-to-agents
(let [a (agent nil)]
(doto (Thread.
(fn []
(binding [*bind-me* :thread-binding]
(send a (constantly *bind-me*)))
(await a)))
(.start)
(.join))
(is (= @a :thread-binding))))
; http://clojure.org/agents
; agent
; deref, @-reader-macro, agent-errors
; send send-off clear-agent-errors
; await await-for
; set-validator get-validator
; add-watch remove-watch
; shutdown-agents
-
12.4. TEST/ANNOTATIONS.CLJ 1541
12.4 test/annotations.clj
test/annotations.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.annotations
(:use clojure.test))
(defn vm-has-ws-annotations?
"Does the vm have the ws annotations we use to test some
annotation features. If not, fall back to Java 5 tests."
[]
(try
(doseq [n ["javax.xml.ws.soap.Addressing"
"javax.xml.ws.WebServiceRef"
"javax.xml.ws.WebServiceRefs"]]
(Class/forName n))
true
(catch ClassNotFoundException e
false)))
(if (vm-has-ws-annotations?)
(load "annotations/java_6_and_later")
(load "annotations/java_5"))
12.5 test/atoms.clj
test/atoms.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.atoms
(:use clojure.test))
; http://clojure.org/atoms
; atom
; deref, @-reader-macro
1542 CHAPTER 12. TEST/CLOJURE
; swap! reset!
; compare-and-set!
12.6 test/clojureset.clj
test/clojureset.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.clojure-set
(:use clojure.test)
(:require [clojure.set :as set]))
(deftest test-union
(are [x y] (= x y)
(set/union) #{}
; identity
(set/union #{}) #{}
(set/union #{1}) #{1}
(set/union #{1 2 3}) #{1 2 3}
; 2 sets
(set/union #{1} #{2}) #{1 2}
(set/union #{1} #{1 2}) #{1 2}
(set/union #{2} #{1 2}) #{1 2}
(set/union #{1 2} #{3}) #{1 2 3}
(set/union #{1 2} #{2 3}) #{1 2 3}
; 3 sets
(set/union #{1 2} #{3 4} #{5 6}) #{1 2 3 4 5 6}
(set/union #{1 2} #{2 3} #{1 3 4}) #{1 2 3 4}
(deftest test-intersection
; at least one argument is needed
(is (thrown? IllegalArgumentException (set/intersection)))
(are [x y] (= x y)
; identity
(set/intersection #{}) #{}
(set/intersection #{1}) #{1}
(set/intersection #{1 2 3}) #{1 2 3}
; 2 sets
(set/intersection #{1 2} #{1 2}) #{1 2}
(set/intersection #{1 2} #{3 4}) #{}
(set/intersection #{1 2} #{1}) #{1}
(set/intersection #{1 2} #{2}) #{2}
(set/intersection #{1 2 4} #{2 3 4 5}) #{2 4}
; 3 sets
(set/intersection #{1 2} #{2 3} #{5 2}) #{2}
(set/intersection #{1 2 3} #{1 3 4} #{1 3}) #{1 3}
(set/intersection #{1 2 3} #{3 4 5} #{8 2 3}) #{3}
(deftest test-difference
(are [x y] (= x y)
; identity
(set/difference #{}) #{}
(set/difference #{1}) #{1}
(set/difference #{1 2 3}) #{1 2 3}
; 2 sets
(set/difference #{1 2} #{1 2}) #{}
(set/difference #{1 2} #{3 4}) #{1 2}
(set/difference #{1 2} #{1}) #{2}
(set/difference #{1 2} #{2}) #{1}
(set/difference #{1 2 4} #{2 3 4 5}) #{1}
; 3 sets
(set/difference #{1 2} #{2 3} #{5 2}) #{1}
(set/difference #{1 2 3} #{1 3 4} #{1 3}) #{2}
(set/difference #{1 2 3} #{3 4 5} #{8 2 3}) #{1} ))
(deftest test-select
(are [x y] (= x y)
(set/select integer? #{}) #{}
(set/select integer? #{1 2}) #{1 2}
(set/select integer? #{1 2 :a :b :c}) #{1 2}
(set/select integer? #{:a :b :c}) #{}) )
(def compositions
#{{:name "Art of the Fugue" :composer "J. S. Bach"}
{:name "Musical Offering" :composer "J. S. Bach"}
{:name "Requiem" :composer "Giuseppe Verdi"}
{:name "Requiem" :composer "W. A. Mozart"}})
(deftest test-project
(are [x y] (= x y)
12.6. TEST/CLOJURESET.CLJ 1545
(deftest test-rename
(are [x y] (= x y)
(set/rename compositions {:name :title})
#{{:title "Art of the Fugue" :composer "J. S. Bach"}
{:title "Musical Offering" :composer "J. S. Bach"}
{:title "Requiem" :composer "Giuseppe Verdi"}
{:title "Requiem" :composer "W. A. Mozart"}}
(set/rename compositions {:year :decade})
#{{:name "Art of the Fugue" :composer "J. S. Bach"}
{:name "Musical Offering" :composer "J. S. Bach"}
{:name "Requiem" :composer "Giuseppe Verdi"}
{:name "Requiem" :composer "W. A. Mozart"}}
(set/rename #{{}} {:year :decade}) #{{}}))
(deftest test-rename-keys
(are [x y] (= x y)
(set/rename-keys {:a "one" :b "two"} {:a :z}) {:z "one" :b "two"}
))
(deftest test-index
(are [x y] (= x y)
(set/index #{{:c 2} {:b 1} {:a 1 :b 2}} [:b]) {{:b 2}
#{{:a 1 :b 2}}, {:b 1} #{{:b 1}} {} #{{:c 2}}}
))
(deftest test-join
(are [x y] (= x y)
(set/join compositions compositions) compositions
(set/join compositions
#{{:name "Art of the Fugue" :genre "Classical"}})
#{{:name "Art of the Fugue" :composer "J. S. Bach"
:genre "Classical"}}
))
(deftest test-map-invert
(are [x y] (= x y)
(set/map-invert {:a "one" :b "two"}) {"one" :a "two" :b}))
(deftest test-subset?
(are [sub super] (set/subset? sub super)
#{} #{}
1546 CHAPTER 12. TEST/CLOJURE
#{} #{1}
#{1} #{1}
#{1 2} #{1 2}
#{1 2} #{1 2 42}
#{false} #{false}
#{nil} #{nil}
#{nil} #{nil false}
#{1 2 nil} #{1 2 nil 4})
(are [notsub super] (not (set/subset? notsub super))
#{1} #{}
#{2} #{1}
#{1 3} #{1}
#{nil} #{false}
#{false} #{nil}
#{false nil} #{nil}
#{1 2 nil} #{1 2}))
(deftest test-superset?
(are [super sub] (set/superset? super sub)
#{} #{}
#{1} #{}
#{1} #{1}
#{1 2} #{1 2}
#{1 2 42} #{1 2}
#{false} #{false}
#{nil} #{nil}
#{false nil} #{false}
#{1 2 4 nil false} #{1 2 nil})
(are [notsuper sub] (not (set/superset? notsuper sub))
#{} #{1}
#{2} #{1}
#{1} #{1 3}
#{nil} #{false}
#{false} #{nil}
#{nil} #{false nil}
#{nil 2 3} #{false nil 2 3}))
12.7 test/clojurexml.clj
test/clojurexml.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.clojure-xml
(:use clojure.test)
(:require [clojure.xml :as xml]))
; parse
; emit-element
; emit
12.8 test/clojurezip.clj
test/clojurezip.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.clojure-zip
(:use clojure.test)
(:require [clojure.zip :as zip]))
; zipper
;
; seq-zip
; vector-zip
; xml-zip
;
; node
; branch?
; children
; make-node
; path
; lefts
; rights
; down
; up
; root
; right
; rightmost
1548 CHAPTER 12. TEST/CLOJURE
; left
; leftmost
;
; insert-left
; insert-right
; replace
; edit
; insert-child
; append-child
; next
; prev
; end?
; remove
12.9 test/compilation.clj
test/compilation.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.compilation
(:use clojure.test))
; http://clojure.org/compilation
; compile
; gen-class, gen-interface
(deftest test-compiler-metadata
(let [m (meta #when)]
(are [x y] (= x y)
(list? (:arglists m)) true
(> (count (:arglists m)) 0) true
(:macro m) true
(:name m) when )))
(deftest test-embedded-constants
(testing "Embedded constants"
(is (eval (= Boolean/TYPE ~Boolean/TYPE)))
(is (eval (= Byte/TYPE ~Byte/TYPE)))
(is (eval (= Character/TYPE ~Character/TYPE)))
(is (eval (= Double/TYPE ~Double/TYPE)))
(is (eval (= Float/TYPE ~Float/TYPE)))
(is (eval (= Integer/TYPE ~Integer/TYPE)))
(is (eval (= Long/TYPE ~Long/TYPE)))
(is (eval (= Short/TYPE ~Short/TYPE)))))
(deftest test-compiler-resolution
(testing
"resolve nonexistent class create should return nil (assembla #262)"
(is (nil? (resolve NonExistentClass.)))))
(deftest test-no-recur-across-try
(testing "dont recur to function from inside try"
(is (thrown? Exception (eval (fn [x] (try (recur 1)))))))
(testing "dont recur to loop from inside try"
(is (thrown? Exception (eval (loop [x] (try (recur 1)))))))
(testing "dont get confused about what the recur is targeting"
(is (thrown? Exception (eval (loop [x] (try (fn [x]) (recur 1)))))))
(testing "dont allow recur accross binding"
(is (thrown? Exception (eval (fn [x] (binding [+ *] (recur 1)))))))
(testing "allow loop/recur inside try"
(is (try
(eval (try (loop [x 3] (if (zero? x) x (recur (dec x))))))
(catch Exception _))))
(testing "allow fn/recur inside try"
(is (try
(eval (try
((fn [x]
(if (zero? x)
x
(recur (dec x))))
3)))
(catch Exception _)))))
-
1550 CHAPTER 12. TEST/CLOJURE
12.10 test/control.clj
test/control.clj
\getchunk{Clojure Copyright}
;;
;; Test "flow control" constructs.
;;
(ns clojure.test-clojure.control
(:use clojure.test
[clojure.test-helper :only (exception)]))
; http://clojure.org/special_forms
; http://clojure.org/macros
(deftest test-do
(are [x y] (= x y)
; no params => nil
(do) nil
; return last
(do 1) 1
(do 1 2) 2
(do 1 2 3 4 5) 5
12.10. TEST/CONTROL.CLJ 1551
; identity (= (do x) x)
(maintains-identity (fn [_] (do _))) )
;; loop/recur
(deftest test-loop
(are [x y] (= x y)
1 (loop []
1)
3 (loop [a 1]
(if (< a 3)
(recur (inc a))
a))
[2 4 6] (loop [a []
b [1 2 3]]
(if (seq b)
(recur (conj a (* 2 (first b)))
(next b))
a))
[6 4 2] (loop [a ()
b [1 2 3]]
(if (seq b)
(recur (conj a (* 2 (first b)))
(next b))
a))
)
)
;; throw, try
(deftest test-when
(are [x y] (= x y)
1 (when true 1)
nil (when true)
nil (when false)
nil (when false (exception))
))
(deftest test-when-not
1552 CHAPTER 12. TEST/CLOJURE
(are [x y] (= x y)
1 (when-not false 1)
nil (when-not true)
nil (when-not false)
nil (when-not true (exception))
))
(deftest test-if-not
(are [x y] (= x y)
1 (if-not false 1)
1 (if-not false 1 (exception))
nil (if-not true 1)
2 (if-not true 1 2)
nil (if-not true (exception))
1 (if-not true (exception) 1)
))
(deftest test-when-let
(are [x y] (= x y)
1 (when-let [a 1]
a)
2 (when-let [[a b] (1 2)]
b)
nil (when-let [a false]
(exception))
))
(deftest test-if-let
(are [x y] (= x y)
1 (if-let [a 1]
a)
2 (if-let [[a b] (1 2)]
b)
nil (if-let [a false]
(exception))
1 (if-let [a false]
a 1)
1 (if-let [[a b] nil]
b 1)
1 (if-let [a false]
(exception)
1)
))
(deftest test-when-first
(are [x y] (= x y)
1 (when-first [a [1 2]]
a)
2 (when-first [[a b] ((1 2) 3)]
b)
12.10. TEST/CONTROL.CLJ 1553
(deftest test-cond
(are [x y] (= x y)
(cond) nil
; false
(are [x] (= (cond x :a true :b) :b)
nil false )
; true
(are [x] (= (cond x :a true :b) :a)
true
0 42
0.0 3.14
2/3
0M 1M
\c
"" "abc"
sym
:kw
() (1 2)
[] [1 2]
{} {:a 1 :b 2}
#{} #{1 2} )
; evaluation
(are [x y] (= x y)
(cond (> 3 2) (+ 1 2) true :result true (exception)) 3
(cond (< 3 2) (+ 1 2) true :result true (exception)) :result )
(deftest test-condp
(are [x] (= :pass x)
(condp = 1
1 :pass
2 :fail)
1554 CHAPTER 12. TEST/CLOJURE
(condp = 1
2 :fail
1 :pass)
(condp = 1
2 :fail
:pass)
(condp = 1
:pass)
(condp = 1
2 :fail
;; doc of condp says result-expr is returned
;; shouldnt it say similar to cond: "evaluates and returns
;; the value of the corresponding expr and doesnt evaluate
;; any of the other tests or exprs."
(identity :pass))
(condp + 1
1 :>> #(if (= % 2) :pass :fail))
(condp + 1
1 :>> #(if (= % 3) :fail :pass))
)
(is (thrown? IllegalArgumentException
(condp = 1)
))
(is (thrown? IllegalArgumentException
(condp = 1
2 :fail)
))
)
(deftest test-dotimes
;; dotimes always returns nil
(is (= nil (dotimes [n 1] n)))
;; test using an atom since dotimes is for modifying
;; test executes n times
(is (= 3
(let [a (atom 0)]
(dotimes [n 3]
(swap! a inc))
@a)
))
;; test all values of n
(is (= [0 1 2]
(let [a (atom [])]
(dotimes [n 3]
(swap! a conj n))
@a)))
(is (= []
12.10. TEST/CONTROL.CLJ 1555
(deftest test-while
(is (= nil (while nil (throw (Exception. "never")))))
(is (= [0 nil]
;; a will dec to 0
;; while always returns nil
(let [a (atom 3)
w (while (pos? @a)
(swap! a dec))]
[@a w])))
(is (thrown? Exception
(while true (throw (Exception. "expected to throw")))))
)
; case
(deftest test-case
(testing "can match many kinds of things"
(let [two 2
test-fn
#(case %
1 :number
"foo" :string
\a :char
pow :symbol
:zap :keyword
(2 \b "bar") :one-of-many
[1 2] :sequential-thing
{:a 2} :map
{:r 2 :d 2} :droid
#{2 3 4 5} :set
[1 [[[2]]]] :deeply-nested
:default)]
(are [result input] (= result (test-fn input))
:number 1
:string "foo"
:char \a
:keyword :zap
:symbol pow
:one-of-many 2
:one-of-many \b
:one-of-many "bar"
:sequential-thing [1 2]
:sequential-thing (list 1 2)
1556 CHAPTER 12. TEST/CLOJURE
:sequential-thing [1 two]
:map {:a 2}
:map {:a two}
:set #{2 3 4 5}
:set #{two 3 4 5}
:default #{2 3 4 5 6}
:droid {:r 2 :d 2}
:deeply-nested [1 [[[two]]]]
:default :anything-not-appearing-above)))
(testing "throws IllegalArgumentException if no match"
(is (thrown-with-msg?
IllegalArgumentException #"No matching clause: 2"
(case 2 1 :ok))))
(testing "sorting doesnt matter"
(let [test-fn
#(case %
{:b 2 :a 1} :map
#{3 2 1} :set
:default)]
(are [result input] (= result (test-fn input))
:map {:a 1 :b 2}
:map (sorted-map :a 1 :b 2)
:set #{3 2 1}
:set (sorted-set 2 1 3))))
(testing "test constants are *not* evaluated"
(let [test-fn
;; never write code like this...
#(case %
(throw (RuntimeException. "boom")) :piece-of-throw-expr
:no-match)]
(are [result input] (= result (test-fn input))
:piece-of-throw-expr throw
:piece-of-throw-expr [RuntimeException. "boom"]
:no-match nil))))
12.11 test/data.clj
test/data.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.data
(:use clojure.data clojure.test))
(deftest diff-test
12.12. TEST/DATASTRUCTURES.CLJ 1557
12.12 test/datastructures.clj
test/datastructures.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.data-structures
(:use clojure.test))
(deftest test-equality
; nil is not equal to any other value
(are [x] (not (= nil x))
true false
1558 CHAPTER 12. TEST/CLOJURE
0 0.0
\space
"" #""
() [] #{} {}
(lazy-seq nil) ; SVN 1292: fixed (= (lazy-seq nil) nil)
(lazy-seq ())
(lazy-seq [])
(lazy-seq {})
(lazy-seq #{})
(lazy-seq "")
(lazy-seq (into-array []))
(new Object) )
; numbers equality across types (see tests below - NOT IMPLEMENTED YET)
; ratios
(is (== 1/2 0.5))
(is (== 1/1000 0.001))
(is (not= 2/3 0.6666666666666666))
;; (array-map :a 1))
;; (all-are (= _1 _2)
;; (sorted-map)
;; (hash-map)
;; (array-map))
;; (all-are (= _1 _2)
;; (sorted-map :a 1)
;; (hash-map :a 1)
;; (array-map :a 1))
;; (all-are (= _1 _2)
;; (sorted-map :a 1 :z 3 :c 2)
;; (hash-map :a 1 :z 3 :c 2)
;; (array-map :a 1 :z 3 :c 2))
(deftest test-count
(let [EMPTY clojure.lang.PersistentQueue/EMPTY]
(are [x y] (= (count x) y)
EMPTY 0
(into EMPTY [:a :b]) 2
(-> (into EMPTY [:a :b]) pop pop) 0
nil 0
() 0
(1) 1
(1 2 3) 3
[] 0
[1] 1
1560 CHAPTER 12. TEST/CLOJURE
[1 2 3] 3
#{} 0
#{1} 1
#{1 2 3} 3
{} 0
{:a 1} 1
{:a 1 :b 2 :c 3} 3
"" 0
"a" 1
"abc" 3
(into-array []) 0
(into-array [1]) 1
(into-array [1 2 3]) 3
(java.util.ArrayList. []) 0
(java.util.ArrayList. [1]) 1
(java.util.ArrayList. [1 2 3]) 3
(java.util.HashMap. {}) 0
(java.util.HashMap. {:a 1}) 1
(java.util.HashMap. {:a 1 :b 2 :c 3}) 3 ))
; different types
(are [x] (= (count [x]) 1)
nil true false
0 0.0 "" \space
() [] #{} {} ))
(deftest test-conj
; doesnt work on strings or arrays
(is (thrown? ClassCastException (conj "" \a)))
(is (thrown? ClassCastException (conj (into-array []) 1)))
(are [x y] (= x y)
(conj nil 1) (1)
(conj nil 3 2 1) (1 2 3)
; list -> conj puts the item at the front of the list
(conj () 1) (1)
(conj () 1 2) (2 1)
12.12. TEST/DATASTRUCTURES.CLJ 1561
(conj (2 3) 1) (1 2 3)
(conj (2 3) 1 4 3) (3 4 1 2 3)
; vector -> conj puts the item at the end of the vector
(conj [] 1) [1]
(conj [] 1 2) [1 2]
(conj [2 3] 1) [2 3 1]
(conj [2 3] 1 4 3) [2 3 1 4 3]
; map -> conj expects another (possibly single entry) map as the
; item, and returns a new map which is the old map plus the
; entries from the new, which may overwrite entries of the old.
; conj also accepts a MapEntry or a vector of two items
;(key and value).
(conj {} {}) {}
(conj {} {:a 1}) {:a 1}
(conj {} {:a 1 :b 2}) {:a 1 :b 2}
(conj {} {:a 1 :b 2} {:c 3}) {:a 1 :b 2 :c 3}
(conj {} {:a 1 :b 2} {:a 3 :c 4}) {:a 3 :b 2 :c 4}
; set
(conj #{} 1) #{1}
1562 CHAPTER 12. TEST/CLOJURE
(deftest test-peek
; doesnt work for sets and maps
(is (thrown? ClassCastException (peek #{1})))
(is (thrown? ClassCastException (peek {:a 1})))
(are [x y] (= x y)
(peek nil) nil
; list = first
(peek ()) nil
(peek (1)) 1
(peek (1 2 3)) 1
; vector = last
(peek []) nil
(peek [1]) 1
(peek [1 2 3]) 3
(deftest test-pop
; doesnt work for sets and maps
12.12. TEST/DATASTRUCTURES.CLJ 1563
(are [x y] (= x y)
(pop nil) nil
(pop (nil)) ()
(pop (1 nil)) (nil)
(pop (nil 2)) (2)
(pop (())) ()
(pop (() nil)) (nil)
(pop (() 2 nil)) (2 nil)
(pop [nil]) []
(pop [1 nil]) [1]
(pop [nil 2]) [nil]
(pop [[]]) []
(pop [[] nil]) [[]]
(pop [[] 2 nil]) [[] 2] ))
(deftest test-list
(are [x] (list? x)
()
()
(list)
(list 1 2 3) )
; order is important
(are [x y] (not (= x y))
(list 1 2) (list 2 1)
(list 3 1 2) (list 1 2 3) )
(are [x y] (= x y)
() ()
(list) ()
1564 CHAPTER 12. TEST/CLOJURE
(list 1) (1)
(list 1 2) (1 2)
; nesting
(list 1 (list 2 3) (list 3 (list 4 5 (list 6 (list 7)))))
(1 (2 3) (3 (4 5 (6 (7)))))
; evaluation
(list (+ 1 2) [(+ 2 3) a] (list (* 2 3) 8))
(3 [5 a] (6 8))
; special cases
(list nil) (nil)
(list 1 nil) (1 nil)
(list nil 2) (nil 2)
(list ()) (())
(list 1 ()) (1 ())
(list () 2) (() 2) ))
(deftest test-find
(are [x y] (= x y)
(find {} :a) nil
(deftest test-contains?
; contains? is designed to work preferably on maps and sets
(are [x y] (= x y)
(contains? {} :a) false
12.12. TEST/DATASTRUCTURES.CLJ 1565
; sets
(contains? #{} 1) false
(contains? #{} nil) false
(contains? [1 2 3] 0) true
(contains? [1 2 3] 2) true
(contains? [1 2 3] 3) false
(contains? [1 2 3] -1) false
; arrays
(contains? (into-array []) 0) false
(contains? (into-array []) -1) false
(contains? (into-array []) 1) false
(deftest test-keys
(are [x y] (= x y) ; other than map data structures
(keys ()) nil
(keys []) nil
(keys #{}) nil
(keys "") nil )
(are [x y] (= x y)
; (class {:a 1}) => clojure.lang.PersistentArrayMap
(keys {}) nil
(keys {:a 1}) (:a)
; (keys {:a 1 :b 2}) (:a :b)
(diff (keys {:a 1 :b 2}) (:a :b)) nil
(deftest test-vals
(are [x y] (= x y) ; other than map data structures
(vals ()) nil
(vals []) nil
(vals #{}) nil
(vals "") nil )
(are [x y] (= x y)
12.12. TEST/DATASTRUCTURES.CLJ 1567
(deftest test-key
(are [x] (= (key (first (hash-map x :value))) x)
nil
false true
0 42
0.0 3.14
2/3
0M 1M
\c
"" "abc"
sym
:kw
() (1 2)
[] [1 2]
{} {:a 1 :b 2}
#{} #{1 2} ))
(deftest test-val
(are [x] (= (val (first (hash-map :key x))) x)
nil
false true
0 42
0.0 3.14
2/3
0M 1M
\c
"" "abc"
sym
:kw
() (1 2)
1568 CHAPTER 12. TEST/CLOJURE
[] [1 2]
{} {:a 1 :b 2}
#{} #{1 2} ))
(deftest test-get
(let [m {:a 1, :b 2, :c {:d 3, :e 4}, :f nil, :g false, nil {:h 5}}]
(is (thrown? IllegalArgumentException (get-in {:a 1} 5)))
(are [x y] (= x y)
(get m :a) 1
(get m :e) nil
(get m :e 0) 0
(get m :b 0) 2
(get m :f 0) nil
(deftest test-hash-set
(are [x] (set? x)
#{}
#{1 2}
(hash-set)
(hash-set 1 2) )
(are [x y] (= x y)
; equal classes
(class #{}) (class (hash-set))
(class #{1 2}) (class (hash-set 1 2))
; creating
(hash-set) #{}
(hash-set 1) #{1}
(hash-set 1 2) #{1 2}
; nesting
(hash-set 1 (hash-set 2 3)
(hash-set 3 (hash-set 4 5 (hash-set 6 (hash-set 7)))))
#{1 #{2 3} #{3 #{4 5 #{6 #{7}}}}}
; evaluation
(hash-set (+ 1 2) [(+ 2 3) :a] (hash-set (* 2 3) 8))
#{3 [5 :a] #{6 8}}
; special cases
(hash-set nil) #{nil}
(hash-set 1 nil) #{1 nil}
(hash-set nil 2) #{nil 2}
(hash-set #{}) #{#{}}
(hash-set 1 #{}) #{1 #{}}
(hash-set #{} 2) #{#{} 2} ))
(deftest test-sorted-set
; only compatible types can be used
(is (thrown? ClassCastException (sorted-set 1 "a")))
(is (thrown? ClassCastException (sorted-set (1 2) [3 4])))
; creates set?
(are [x] (set? x)
(sorted-set)
(sorted-set 1 2) )
nil
false true
0 42
0.0 3.14
2/3
0M 1M
\c
"" "abc"
sym
:kw
() ; (1 2)
[] [1 2]
{} ; {:a 1 :b 2}
#{} ; #{1 2}
)
; cannot be cast to java.lang.Comparable
(is (thrown? ClassCastException (sorted-set (1 2) (1 2))))
(is (thrown? ClassCastException (sorted-set {:a 1 :b 2} {:a 1 :b 2})))
(is (thrown? ClassCastException (sorted-set #{1 2} #{1 2})))
(are [x y] (= x y)
; generating
(sorted-set) #{}
(sorted-set 1) #{1}
(sorted-set 1 2) #{1 2}
; sorting
(seq (sorted-set 5 4 3 2 1)) (1 2 3 4 5)
; special cases
(sorted-set nil) #{nil}
(sorted-set 1 nil) #{nil 1}
(sorted-set nil 2) #{nil 2}
(sorted-set #{}) #{#{}} ))
(deftest test-sorted-set-by
; only compatible types can be used
; NB: not a ClassCastException, but a RuntimeException is thrown,
; requires discussion on whether this should be symmetric with
; test-sorted-set
; creates set?
(are [x] (set? x)
(sorted-set-by <)
(sorted-set-by < 1 2) )
12.12. TEST/DATASTRUCTURES.CLJ 1571
(are [x y] (= x y)
; generating
(sorted-set-by >) #{}
(sorted-set-by > 1) #{1}
(sorted-set-by > 1 2) #{1 2}
; sorting
(seq (sorted-set-by < 5 4 3 2 1)) (1 2 3 4 5)
; special cases
(sorted-set-by compare nil) #{nil}
(sorted-set-by compare 1 nil) #{nil 1}
(sorted-set-by compare nil 2) #{nil 2}
(sorted-set-by compare #{}) #{#{}} ))
(deftest test-set
; set?
(are [x] (set? (set x))
() (1 2)
[] [1 2]
1572 CHAPTER 12. TEST/CLOJURE
#{} #{1 2}
{} {:a 1 :b 2}
(into-array []) (into-array [1 2])
"" "abc" )
; unique
(are [x] (= (set [x x]) #{x})
nil
false true
0 42
0.0 3.14
2/3
0M 1M
\c
"" "abc"
sym
:kw
() (1 2)
[] [1 2]
{} {:a 1 :b 2}
#{} #{1 2} )
; conversion
(are [x y] (= (set x) y)
() #{}
(1 2) #{1 2}
[] #{}
[1 2] #{1 2}
{} #{}
{:a 1 :b 2} #{[:a 1] [:b 2]}
"" #{}
"abc" #{\a \b \c} ))
(deftest test-disj
; doesnt work on lists, vectors or maps
(is (thrown? ClassCastException (disj (1 2) 1)))
(is (thrown? ClassCastException (disj [1 2] 1)))
(is (thrown? ClassCastException (disj {:a 1} :a)))
; identity
12.12. TEST/DATASTRUCTURES.CLJ 1573
; type identity
(are [x] (= (class (disj x)) (class x))
(hash-set)
(hash-set 1 2)
(sorted-set)
(sorted-set 1 2) )
(are [x y] (= x y)
(disj nil :a) nil
(disj nil :a :b) nil
(deftest test-queues
(let [EMPTY clojure.lang.PersistentQueue/EMPTY]
(are [x y] (= x y)
EMPTY EMPTY
(into EMPTY (range 50)) (into EMPTY (range 50))
(range 5) (into EMPTY (range 5))
(range 1 6) (-> EMPTY
(into (range 6))
pop))
(are [x y] (not= x y)
(range 5) (into EMPTY (range 6))
(range 6) (into EMPTY (range 5))
(range 0 6) (-> EMPTY
(into (range 6))
pop)
(range 1 6) (-> EMPTY
(into (range 7))
pop))))
12.13 test/def.clj
test/def.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.def
(:use clojure.test clojure.test-helper
clojure.test-clojure.protocols))
(deftest defn-error-messages
(testing "bad arglist forms"
(is (fails-with-cause? IllegalArgumentException
#"Parameter declaration arg1 should be a vector"
(eval-in-temp-ns (defn foo (arg1 arg2)))))))
(deftest dynamic-redefinition
;; too many contextual things for this kind of caching to work...
(testing "classes are never cached, even if their bodies are the same"
(is (= :b
(eval
(do
12.14. TEST/ERRORS.CLJ 1575
12.14 test/errors.clj
test/errors.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.errors
(:use clojure.test)
(:import clojure.lang.ArityException))
(defn f0 [] 0)
(defn f1 [a] a)
(deftest arity-exception
;; IllegalArgumentException is pre-1.3
(is (thrown-with-msg? IllegalArgumentException
#"Wrong number of args \(1\) passed to"
(f0 1)))
(is (thrown-with-msg? ArityException
#"Wrong number of args \(0\) passed to"
(f1)))
(is (thrown-with-msg? ArityException
#"Wrong number of args \(1\) passed to"
(macroexpand (m0 1))))
(is (thrown-with-msg? ArityException
#"Wrong number of args \(2\) passed to"
(macroexpand (m1 1 2)))))
-
1576 CHAPTER 12. TEST/CLOJURE
12.15 test/evaluation.clj
test/evaluation.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.evaluation
(:use clojure.test))
(defmacro test-that
"Provides a useful way for specifying the purpose of tests. If the
first-level forms are lists that make a call to a clojure.test
function, it supplies the purpose as the msg argument to those
functions. Otherwise, the purpose just acts like a comment and
the forms are run unchanged."
[purpose & test-forms]
(let [tests (map
#(if (= (:ns (meta (resolve (first %))))
(the-ns clojure.test))
(concat % (list purpose))
%)
test-forms)]
(do ~@tests)))
(deftest Eval
(is (= (eval (+ 1 2 3)) (Compiler/eval (+ 1 2 3))))
(is (= (eval (list 1 2 3)) (1 2 3)))
(is (= (eval (list + 1 2 3)) (list clojure.core/+ 1 2 3)))
(test-that "Non-closure fns are supported as code"
(is (= (eval (eval (list + 1 2 3))) 6)))
(is (= (eval (list + 1 2 3)) 6)))
(deftest Literals
; Strings, numbers, characters, nil and keywords should evaluate
; to themselves
(evaluates-to-itself? "test")
(evaluates-to-itself? "test
multi-line
string")
(evaluates-to-itself? 1)
(evaluates-to-itself? 1.0)
(evaluates-to-itself? 1.123456789)
(evaluates-to-itself? 1/2)
(evaluates-to-itself? 1M)
(evaluates-to-itself? 999999999999999999)
(evaluates-to-itself? \a)
(evaluates-to-itself? \newline)
(evaluates-to-itself? nil)
(evaluates-to-itself? :test)
; Boolean literals should evaluate to Boolean.{TRUE|FALSE}
(is (identical? (eval true) Boolean/TRUE))
(is (identical? (eval false) Boolean/FALSE)))
(defmacro throws-with-msg
([re form] (throws-with-msg ~re ~form Exception))
([re form x] (throws-with-msg
~re
~form
~(if (instance? Exception x) x Exception)
~(if (instance? String x) x nil)))
([re form class msg]
1578 CHAPTER 12. TEST/CLOJURE
(deftest SymbolResolution
(test-that
"If a symbol is namespace-qualified, the evaluated value is the value
of the binding of the global var named by the symbol"
(is (= (eval resolution-test/bar) 123)))
(test-that
"It is an error if there is no global var named by the symbol"
(throws-with-msg
#".*Unable to resolve symbol: bar.*" (eval bar)))
(test-that
"It is an error if the symbol reference is to a non-public var in a
different namespace"
(throws-with-msg
#".*resolution-test/baz is not public.*"
(eval resolution-test/baz)
Compiler$CompilerException))
(test-that
"If a symbol is package-qualified, its value is the Java class
named by the symbol"
(is (= (eval java.lang.Math) (class-for-name "java.lang.Math"))))
(test-that
"If a symbol is package-qualified, it is an error if there is
no Class named by the symbol"
(is (thrown? Compiler$CompilerException (eval java.lang.FooBar))))
(test-that
"If a symbol is not qualified, the following applies, in this order:
5. It is an error."
; First
(doall (for [form (def if do let quote var fn loop recur throw try
monitor-enter monitor-exit)]
(is (thrown? Compiler$CompilerException (eval form)))))
(let [if "foo"]
(is (thrown? Compiler$CompilerException (eval if)))
; Second
(is (= (eval Boolean) (class-for-name "java.lang.Boolean"))))
(let [Boolean "foo"]
(is (= (eval Boolean) (class-for-name "java.lang.Boolean"))))
; Third
(is (= (eval (let [foo "bar"] foo)) "bar"))
; Fourth
(in-test-ns (is (= (eval foo) "abc")))
(is (thrown? Compiler$CompilerException
(eval bar))) ; not in this namespace
; Fifth
(is (thrown? Compiler$CompilerException (eval foobar)))))
(deftest Metadata
(test-that
"find returns key symbols and their metadata"
(let [s (struct struct-with-symbols 1)]
(is (= {:a "A"} (meta (first (find s k))))))))
(deftest Collections
1580 CHAPTER 12. TEST/CLOJURE
(in-test-ns
(test-that
"Vectors and Maps yield vectors and (hash) maps whose contents
are the evaluated values of the objects they contain."
(is (= (eval [x y 3]) [1 2 3]))
(is (= (eval {:x x :y y :z 3}) {:x 1 :y 2 :z 3}))
(is (instance? clojure.lang.IPersistentMap (eval {:x x :y y})))))
(in-test-ns
(test-that
"Metadata maps yield maps whose contents are the evaluated values
of the objects they contain. If a vector or map has metadata, the
evaluated metadata map will become the metadata of the resulting
value."
(is (= (eval #^{:x x} [x y]) #^{:x 1} [1 2]))))
(test-that
"An empty list () evaluates to an empty list."
(is (= (eval ()) ()))
(is (empty? (eval ())))
(is (= (eval (list)) ())))
(deftest Macros)
(deftest Loading)
12.16 test/for.clj
test/for.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.for
(:use clojure.test))
12.16. TEST/FOR.CLJ 1581
(deftest Docstring-Example
(is (= (take 100 (for [x (range 100000000)
y (range 1000000) :while (< y x)]
[x y]))
([1 0] [2 0] [2 1] [3 0] [3 1] [3 2] [4 0] [4 1] [4 2] [4 3]
[5 0] [5 1] [5 2] [5 3] [5 4]
[6 0] [6 1] [6 2] [6 3] [6 4] [6 5]
[7 0] [7 1] [7 2] [7 3] [7 4] [7 5] [7 6]
[8 0] [8 1] [8 2] [8 3] [8 4] [8 5] [8 6] [8 7]
[9 0] [9 1] [9 2] [9 3] [9 4] [9 5] [9 6] [9 7] [9 8]
[10 0] [10 1] [10 2] [10 3] [10 4] [10 5] [10 6] [10 7]
[10 8] [10 9]
[11 0] [11 1] [11 2] [11 3] [11 4] [11 5] [11 6] [11 7]
[11 8] [11 9] [11 10]
[12 0] [12 1] [12 2] [12 3] [12 4] [12 5] [12 6] [12 7]
[12 8] [12 9] [12 10] [12 11]
[13 0] [13 1] [13 2] [13 3] [13 4] [13 5] [13 6] [13 7]
[13 8] [13 9] [13 10] [13 11] [13 12]
[14 0] [14 1] [14 2] [14 3] [14 4] [14 5] [14 6] [14 7]
[14 8]))))
(deftest-both When
(is (= (for [x (range 10) :when (odd? x)] x) (1 3 5 7 9)))
(is (= (for [x (range 4) y (range 4) :when (odd? y)] [x y])
([0 1] [0 3] [1 1] [1 3] [2 1] [2 3] [3 1] [3 3])))
(is (= (for [x (range 4) y (range 4) :when (odd? x)] [x y])
([1 0] [1 1] [1 2] [1 3] [3 0] [3 1] [3 2] [3 3])))
(is (= (for [x (range 4) :when (odd? x) y (range 4)] [x y])
([1 0] [1 1] [1 2] [1 3] [3 0] [3 1] [3 2] [3 3])))
(is (= (for [x (range 5) y (range 5) :when (< x y)] [x y])
([0 1] [0 2] [0 3] [0 4] [1 2] [1 3] [1 4] [2 3] [2 4] [3 4]))))
(defn only
"Returns a lazy seq of increasing ints starting at 0. Trying to get
the nth+1 value of the seq throws an exception. This is meant to
help detecting over-eagerness in lazy seq consumers."
[n]
(lazy-cat (range n)
1582 CHAPTER 12. TEST/CLOJURE
(deftest-both While
(is (= (for [x (only 6) :while (< x 5)] x) (0 1 2 3 4)))
(is (= (for [x (range 4) y (only 4) :while (< y 3)] [x y])
([0 0] [0 1] [0 2] [1 0] [1 1] [1 2]
[2 0] [2 1] [2 2] [3 0] [3 1] [3 2])))
(is (= (for [x (range 4) y (range 4) :while (< x 3)] [x y])
([0 0] [0 1] [0 2] [0 3] [1 0] [1 1] [1 2] [1 3]
[2 0] [2 1] [2 2] [2 3])))
(is (= (for [x (only 4) :while (< x 3) y (range 4)] [x y])
([0 0] [0 1] [0 2] [0 3] [1 0] [1 1] [1 2] [1 3]
[2 0] [2 1] [2 2] [2 3])))
(is (= (for [x (range 4) y (range 4) :while (even? x)] [x y])
([0 0] [0 1] [0 2] [0 3] [2 0] [2 1] [2 2] [2 3])))
(is (= (for [x (only 2) :while (even? x) y (range 4)] [x y])
([0 0] [0 1] [0 2] [0 3])))
(is (= (for [x (range 4) y (only 4) :while (< y x)] [x y])
([1 0] [2 0] [2 1] [3 0] [3 1] [3 2]))))
(deftest-both While-and-When
(is
(= (for [x (only 6) :while (< x 5) y (range 4) :when (odd? y)] [x y])
([0 1] [0 3] [1 1] [1 3] [2 1] [2 3] [3 1] [3 3] [4 1] [4 3])))
(is
(= (for [x (range 4) :when (odd? x) y (only 6) :while (< y 5)] [x y])
([1 0] [1 1] [1 2] [1 3] [1 4] [3 0] [3 1] [3 2] [3 3] [3 4])))
(is
(= (for [x (only 6) :while (< x 5) y (range 4) :when (odd? (+ x y))]
[x y])
([0 1] [0 3] [1 0] [1 2] [2 1] [2 3] [3 0] [3 2] [4 1] [4 3])))
(is
(= (for [x (range 4) :when (odd? x) y (only 2) :while (odd? (+ x y))]
[x y])
([1 0] [3 0]))))
(deftest-both While-and-When-Same-Binding
(is (= (for [x (only 6) :while (< x 5) :when (odd? x)] x) (1 3)))
(is (= (for [x (only 6)
:while (< x 5) ; if :while is false, :when should not be evaled
:when (do (if (< x 5) (odd? x)))] x) (1 3)))
(is (= (for [a (range -2 5)
:when (not= a 0) ; :when may guard :while
:while (> (Math/abs (/ 1.0 a)) 1/3)] a) (-2 -1 1 2))))
(deftest-both Nesting
(is (= (for [x (a b) y (interpose x (1 2)) z (list x y)] [x y z])
([a 1 a] [a 1 1] [a a a] [a a a] [a 2 a] [a 2 2]
[b 1 b] [b 1 1] [b b b] [b b b] [b 2 b] [b 2 2])))
(is (= (for [x [a nil] y [x b]] [x y])
12.17. TEST/GENCLASS.CLJ 1583
(deftest-both Destructuring
(is (= (for [{:syms [a b c]}
(map #(zipmap (a b c) (range % 5)) (range 3))
x [a b c]]
(Integer. (str a b c x)))
(120 121 122 1231 1232 1233 2342 2343 2344))))
(deftest-both Let
(is (= (for [x (range 3) y (range 3) :let [z (+ x y)]
:when (odd? z)] [x y z])
([0 1 1] [1 0 1] [1 2 3] [2 1 3])))
(is (= (for [x (range 6) :let [y (rem x 2)]
:when (even? y) z [8 9]] [x z])
([0 8] [0 9] [2 8] [2 9] [4 8] [4 9]))))
12.17 test/genclass.clj
test/genclass.clj
\getchunk{Clojure Copyright}
(deftest arg-support
(let [example (ExampleClass.)
o (Object.)]
(is (= "foo with o, o" (.foo example o o)))
(is (= "foo with o, i" (.foo example o (int 1))))
(is (thrown? java.lang.UnsupportedOperationException
1584 CHAPTER 12. TEST/CLOJURE
(deftest name-munging
(testing "mapping from Java fields to Clojure vars"
(is (= #clojure.test-clojure.genclass.examples/-foo-Object-int
(get-field ExampleClass foo_Object_int__var)))
(is (= #clojure.test-clojure.genclass.examples/-toString
(get-field ExampleClass toString__var)))))
(deftest genclass-option-validation
(is (fails-with-cause? IllegalArgumentException
#"Not a valid method name: has-hyphen"
(@#clojure.core/validate-generate-class-options
{:methods [[fine [] void] [has-hyphen [] void]]}))))
-
12.18. TEST/JAVAINTEROP.CLJ 1585
12.18 test/javainterop.clj
test/javainterop.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.java-interop
(:use clojure.test))
; http://clojure.org/java_interop
; http://clojure.org/compilation
(deftest test-dot
; (.instanceMember instance args*)
(are [x] (= x "FRED")
(.toUpperCase "fred")
(. "fred" toUpperCase)
(. "fred" (toUpperCase)) )
; (Classname/staticMethod args*)
(are [x] (= x 7)
(Math/abs -7)
(. Math abs -7)
(. Math (abs -7)) )
; Classname/staticField
(are [x] (= x 2147483647)
Integer/MAX_VALUE
(. Integer MAX_VALUE) ))
(deftest test-double-dot
(is (= (.. System (getProperties) (get "os.name"))
1586 CHAPTER 12. TEST/CLOJURE
(deftest test-doto
(let [m (doto (new java.util.HashMap)
(.put "a" 1)
(.put "b" 2))]
(are [x y] (= x y)
(class m) java.util.HashMap
m {"a" 1 "b" 2} )))
(deftest test-new
; Integer
(are [expr cls value] (and (= (class expr) cls)
(= expr value))
(new java.lang.Integer 42) java.lang.Integer 42
(java.lang.Integer. 123) java.lang.Integer 123 )
; Date
(are [x] (= (class x) java.util.Date)
(new java.util.Date)
(java.util.Date.) ))
(deftest test-instance?
; evaluation
(are [x y] (= x y)
(instance? java.lang.Integer (+ 1 2)) false
(instance? java.lang.Long (+ 1 2)) true )
; different types
(are [type literal] (instance? literal type)
1 java.lang.Long
1.0 java.lang.Double
1M java.math.BigDecimal
\a java.lang.Character
"a" java.lang.String )
; set!
; memfn
12.18. TEST/JAVAINTEROP.CLJ 1587
(deftest test-bean
(let [b (bean java.awt.Color/black)]
(are [x y] (= x y)
(map? b) true
(:red b) 0
(:green b) 0
(:blue b) 0
(:RGB b) -16777216
(:alpha b) 255
(:transparency b) 1
; proxy, proxy-super
(deftest test-proxy-chain
(testing "That the proxy functions can chain"
(are [x y] (= x y)
(-> (get-proxy-class Object)
construct-proxy
(init-proxy {})
(update-proxy {"toString" (fn [_] "chain chain chain")})
str)
"chain chain chain"
(deftest test-bases
(are [x y] (= x y)
(bases java.lang.Math)
(list java.lang.Object)
(bases java.lang.Integer)
(list java.lang.Number java.lang.Comparable) ))
(deftest test-supers
(are [x y] (= x y)
(supers java.lang.Math)
#{java.lang.Object}
(supers java.lang.Integer)
#{java.lang.Number java.lang.Object
java.lang.Comparable java.io.Serializable} ))
1588 CHAPTER 12. TEST/CLOJURE
; copy of a sequence
(are [x] (and (= (alength (~type-array x)) (count x))
(= (vec (~type-array x)) x))
[]
[1]
[1 -2 3 0 5] )
(deftest test-type-array-exceptions
(are [x] (thrown? NegativeArraySizeException x)
(int-array -1)
(long-array -1)
(float-array -1)
(double-array -1) ))
(deftest test-make-array
; negative size
(is (thrown? NegativeArraySizeException (make-array Integer -1)))
; one-dimensional
(are [x] (= (alength (make-array Integer x)) x)
0 1 5 )
; multi-dimensional
(let [a (make-array Long 3 2 4)]
(aset a 0 1 2 987)
(are [x y] (= x y)
(alength a) 3
(alength (first a)) 2
(alength (first (first a))) 4
(aget a 0 1 2) 987
(class (aget a 0 1 2)) Long )))
(deftest test-to-array
(let [v [1 "abc" :kw \c []]
a (to-array v)]
(are [x y] (= x y)
; length
(alength a) (count v)
; content
(vec a) v
(class (aget a 0)) (class (nth v 0))
(class (aget a 1)) (class (nth v 1))
(class (aget a 2)) (class (nth v 2))
(class (aget a 3)) (class (nth v 3))
(class (aget a 4)) (class (nth v 4)) ))
(int-array 0)
(int-array [1 2 3])
(to-array [])
(to-array [1 2 3]) ))
(deftest test-into-array
; compatible types only
(is (thrown? IllegalArgumentException
(into-array [1 "abc" :kw])))
(is (thrown? IllegalArgumentException
(into-array [1.2 4])))
(is (thrown? IllegalArgumentException
(into-array [(byte 2) (short 3)])))
(is (thrown? IllegalArgumentException
(into-array Byte/TYPE [100000000000000])))
; simple case
(let [v [1 2 3 4 5]
a (into-array v)]
(are [x y] (= x y)
(alength a) (count v)
(vec a) v
(class (first a)) (class (first v)) ))
(int-array 0)
(int-array [1 2 3])
(to-array [])
(to-array [1 2 3]) ))
(deftest test-to-array-2d
; needs to be a collection of collection(s)
(is (thrown? Exception (to-array-2d [1 2 3])))
; ragged array
(let [v [[1] [2 3] [4 5 6]]
a (to-array-2d v)]
(are [x y] (= x y)
(alength a) (count v)
(alength (aget a 0)) (count (nth v 0))
(alength (aget a 1)) (count (nth v 1))
(alength (aget a 2)) (count (nth v 2))
; empty array
(let [a (to-array-2d [])]
(are [x y] (= x y)
(alength a) 0
(vec a) [] )))
(deftest test-alength
(are [x] (= (alength x) 0)
(int-array 0)
(long-array 0)
(float-array 0)
(double-array 0)
1592 CHAPTER 12. TEST/CLOJURE
(boolean-array 0)
(byte-array 0)
(char-array 0)
(short-array 0)
(make-array Integer/TYPE 0)
(to-array [])
(into-array [])
(to-array-2d []) )
(deftest test-aclone
; clone all arrays except 2D
(are [x] (and (= (alength (aclone x)) (alength x))
(= (vec (aclone x)) (vec x)))
(int-array 0)
(long-array 0)
(float-array 0)
(double-array 0)
(boolean-array 0)
(byte-array 0)
(char-array 0)
(short-array 0)
12.18. TEST/JAVAINTEROP.CLJ 1593
(make-array Integer/TYPE 0)
(to-array [])
(into-array [])
(int-array [1 2 3])
(long-array [1 2 3])
(float-array [1 2 3])
(double-array [1 2 3])
(boolean-array [true false])
(byte-array [(byte 1) (byte 2)])
(char-array [\a \b \c])
(short-array [(short 1) (short 2)])
(make-array Integer/TYPE 3)
(to-array [1 "a" :k])
(into-array [1 2 3]) )
; clone 2D
(are [x] (and (= (alength (aclone x)) (alength x))
(= (map alength (aclone x)) (map alength x))
(= (map vec (aclone x)) (map vec x)))
(to-array-2d [])
(to-array-2d [[1] [2 3] [4 5 6]]) ))
(deftest test-boolean
(are [x y] (and (instance? java.lang.Boolean (boolean x))
(= (boolean x) y))
nil false
false false
true true
0 true
1 true
() true
[1] true
"" true
\space true
:kw true ))
(deftest test-char
; int -> char
1594 CHAPTER 12. TEST/CLOJURE
12.19 test/keywords.clj
test/keywords.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.keywords
(:use clojure.test))
12.20 test/logic.clj
test/logic.clj
\getchunk{Clojure Copyright}
;;
;; Created 1/29/2009
(ns clojure.test-clojure.logic
(:use clojure.test
[clojure.test-helper :only (exception)]))
(deftest test-if
; true/false/nil
(are [x y] (= x y)
(if true :t) :t
(if true :t :f) :t
(if true :t (exception)) :t
; zero/empty is true
(are [x] (= (if x :t :f) :t)
(byte 0)
(short 0)
(int 0)
(long 0)
(bigint 0)
(float 0)
(double 0)
(bigdec 0)
0/2
""
#""
(symbol "")
()
[]
{}
#{}
(into-array []) )
(short 2)
(int 2)
(long 2)
(bigint 2)
(float 2)
(double 2)
(bigdec 2)
2/3
\a
"abc"
#"a*b"
abc
:kw
(1 2)
[1 2]
{:a 1 :b 2}
#{1 2}
(into-array [1 2])
(new java.util.Date) ))
(deftest test-nil-punning
(are [x y] (= (if x :no :yes) y)
(first []) :yes
(next [1]) :yes
(rest [1]) :no
(concat) :no
(concat []) :no
(deftest test-and
(are [x y] (= x y)
(and) true
(and true) true
(and nil) nil
(and false) false
(deftest test-or
(are [x y] (= x y)
(or) nil
(or true) true
(or nil) nil
(or false) false
(deftest test-not
; (is (thrown? IllegalArgumentException (not)))
(are [x] (= (not x) true)
nil
false )
(are [x] (= (not x) false)
1598 CHAPTER 12. TEST/CLOJURE
true
; numbers
0
0.0
42
1.2
0/2
2/3
; characters
\space
\tab
\a
; strings
""
"abc"
; regexes
#""
#"a*b"
; symbols
(symbol "")
abc
; keywords
:kw
; collections/arrays
()
(1 2)
[]
[1 2]
{}
{:a 1 :b 2}
#{}
#{1 2}
(into-array [])
(into-array [1 2])
; Java objects
(new java.util.Date) ))
-
12.21. TEST/MACROS.CLJ 1599
12.21 test/macros.clj
test/macros.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.macros
(:use clojure.test))
; http://clojure.org/macros
; ->
; defmacro definline macroexpand-1 macroexpand
12.22 test/main.clj
test/main.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.main
(:use clojure.test
[clojure.test-helper :only [platform-newlines]])
(:require [clojure.main :as main]))
(deftest eval-opt
(testing "evals and prints forms"
(is (= (platform-newlines "2\n4\n")
(with-out-str (#clojure.main/eval-opt "(+ 1 1) (+ 2 2)")))))
(defmacro with-err-str
"Evaluates exprs in a context in which *err* is bound to a fresh
StringWriter. Returns the string created by any nested printing
calls."
[& body]
(let [s# (new java.io.StringWriter)
p# (new java.io.PrintWriter s#)]
(binding [*err* p#]
~@body
(str s#))))
(defn run-repl-and-return-err
"Run repl, swallowing stdout and returing stderr."
[in-str]
(with-err-str
(with-out-str
(with-in-str in-str
(main/repl)))))
12.23 test/metadata.clj
test/metadata.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.metadata
(:use clojure.test
[clojure.test-helper :only (eval-in-temp-ns)]))
(def public-namespaces
[clojure.core
clojure.pprint
clojure.inspector
clojure.set
12.23. TEST/METADATA.CLJ 1601
clojure.stacktrace
clojure.test
clojure.walk
clojure.xml
clojure.zip
clojure.java.io
clojure.java.browse
clojure.java.javadoc
clojure.java.shell
clojure.string
clojure.data])
(def public-vars
(mapcat #(vals (ns-publics %)) public-namespaces))
(def public-vars-with-docstrings
(filter (comp :doc meta) public-vars))
(deftest public-vars-with-docstrings-have-added
(is (= [] (remove (comp :added meta) public-vars-with-docstrings))))
(deftest interaction-of-def-with-metadata
(testing "initial def sets metadata"
(let [v (eval-in-temp-ns
(def ^{:a 1} foo 0)
#foo)]
(is (= 1 (-> v meta :a)))))
#_(testing "subsequent declare doesnt overwrite metadata"
(let [v (eval-in-temp-ns
(def ^{:b 2} bar 0)
(declare bar)
#bar)]
(is (= 2 (-> v meta :b))))
(testing "when compiled"
(let [v (eval-in-temp-ns
(def ^{:c 3} bar 0)
(defn declare-bar []
(declare bar))
(declare-bar)
#bar)]
(is (= 3 (-> v meta :c))))))
(testing "subsequent def with init-expr *does* overwrite metadata"
(let [v (eval-in-temp-ns
(def ^{:d 4} quux 0)
(def quux 1)
#quux)]
(is (nil? (-> v meta :d))))
1602 CHAPTER 12. TEST/CLOJURE
12.24 test/multimethods.clj
test/multimethods.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.multimethods
(:use clojure.test [clojure.test-helper :only (with-var-roots)])
(:require [clojure.set :as set]))
; http://clojure.org/multimethods
; defmulti
; defmethod
; remove-method
; prefer-method
; methods
; prefers
(defmacro for-all
[& args]
(dorun (for ~@args)))
(defn hierarchy-tags
"Return all tags in a derivation hierarchy"
[h]
(set/select
#(instance? clojure.lang.Named %)
(reduce into #{} (map keys (vals h)))))
(defn transitive-closure
"Return all objects reachable by calling f starting with o,
not including o itself. f should return a collection."
[o f]
12.24. TEST/MULTIMETHODS.CLJ 1603
(defn tag-descendants
"Set of descedants which are tags (i.e. Named)."
[& args]
(set/select
#(instance? clojure.lang.Named %)
(or (apply descendants args) #{})))
(defn assert-valid-hierarchy
[h]
(let [tags (hierarchy-tags h)]
(testing "ancestors are the transitive closure of parents"
(for-all [tag tags]
(is (= (transitive-closure tag #(parents h %))
(or (ancestors h tag) #{})))))
(testing "ancestors are transitive"
(for-all [tag tags]
(is (= (transitive-closure tag #(ancestors h %))
(or (ancestors h tag) #{})))))
(testing "tag descendants are transitive"
(for-all [tag tags]
(is (= (transitive-closure tag #(tag-descendants h %))
(or (tag-descendants h tag) #{})))))
(testing "a tag isa? all of its parents"
(for-all [tag tags
:let [parents (parents h tag)]
parent parents]
(is (isa? h tag parent))))
(testing "a tag isa? all of its ancestors"
(for-all [tag tags
:let [ancestors (ancestors h tag)]
ancestor ancestors]
(is (isa? h tag ancestor))))
(testing "all my descendants have me as an ancestor"
(for-all [tag tags
:let [descendants (descendants h tag)]
descendant descendants]
(is (isa? h descendant tag))))
(testing "there are no cycles in parents"
(for-all [tag tags]
(is (not (contains?
(transitive-closure tag #(parents h %)) tag)))))
(testing "there are no cycles in descendants"
1604 CHAPTER 12. TEST/CLOJURE
(def family
(reduce #(apply derive (cons %1 %2)) (make-hierarchy)
[[::parent-1 ::ancestor-1]
[::parent-1 ::ancestor-2]
[::parent-2 ::ancestor-2]
[::child ::parent-2]
[::child ::parent-1]]))
;tpd this should be a regex, but i dont know how to split a regex
;(deftest cycles-are-forbidden
; (testing "a tag cannot be its own parent"
; (is (thrown-with-msg? Throwable #"\(not= tag parent\)"
; (derive family ::child ::child))))
; (testing "a tag cannot be its own ancestor"
; (is
; (thrown-with-msg? Throwable
; (str "Cyclic derivation: :clojure.test-clojure.multimethods/child "
; "has :clojure.test-clojure.multimethods/ancestor-1 as ancestor")
; (derive family ::ancestor-1 ::child)))))
(deftest using-diamond-inheritance
(let [diamond (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
[[::mammal ::animal]
[::bird ::animal]
[::griffin ::mammal]
[::griffin ::bird]])
bird-no-more (underive diamond ::griffin ::bird)]
(assert-valid-hierarchy diamond)
(assert-valid-hierarchy bird-no-more)
(testing "a griffin is a mammal, indirectly through mammal and bird"
(is (isa? diamond ::griffin ::animal)))
(testing "a griffin is a bird"
(is (isa? diamond ::griffin ::bird)))
(testing "after underive, griffin is no longer a bird"
(is (not (isa? bird-no-more ::griffin ::bird))))
(testing "but it is still an animal, via mammal"
(is (isa? bird-no-more ::griffin ::animal)))))
(deftest derivation-world-bridges-to-java-inheritance
(let [h (derive (make-hierarchy) java.util.Map ::map)]
(testing "a Java class can be isa? a tag"
(is (isa? h java.util.Map ::map)))
(testing "if a Java class isa? a tag, so are its subclasses..."
(is (isa? h java.util.HashMap ::map)))
(testing "...but not its superclasses!"
(is (not (isa? h java.util.Collection ::map))))))
12.25. TEST/NSLIBS.CLJ 1605
(deftest global-hierarchy-test
(with-var-roots {#clojure.core/global-hierarchy (make-hierarchy)}
(assert-valid-hierarchy @#clojure.core/global-hierarchy)
(testing "when you add some derivations..."
(derive ::lion ::cat)
(derive ::manx ::cat)
(assert-valid-hierarchy @#clojure.core/global-hierarchy))
(testing "...isa? sees the derivations"
(is (isa? ::lion ::cat))
(is (not (isa? ::cat ::lion))))
(testing "... you can traverse the derivations"
(is (= #{::manx ::lion} (descendants ::cat)))
(is (= #{::cat} (parents ::manx)))
(is (= #{::cat} (ancestors ::manx))))
(testing "then, remove a derivation..."
(underive ::manx ::cat))
(testing "... traversals update accordingly"
(is (= #{::lion} (descendants ::cat)))
(is (nil? (parents ::manx)))
(is (nil? (ancestors ::manx))))))
#_(defmacro for-all
"Better than the actual for-all, if only it worked."
[& args]
(reduce
#(and %1 %2)
(map true? (for ~@args))))
12.25 test/nslibs.clj
test/nslibs.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.ns-libs
(:use clojure.test))
; http://clojure.org/namespaces
; in-ns ns create-ns
; alias import intern refer
; all-ns find-ns
1606 CHAPTER 12. TEST/CLOJURE
; http://clojure.org/libs
; require use
; loaded-libs
(deftest test-alias
(is (thrown-with-msg? Exception
#"No namespace: epicfail found" (alias bogus epicfail))))
(deftest test-require
(is (thrown? Exception (require :foo)))
(is (thrown? Exception (require))))
(deftest test-use
(is (thrown? Exception (use :foo)))
(is (thrown? Exception (use))))
(deftest reimporting-deftypes
(let [inst1 (binding [*ns* *ns*]
(eval (do (ns exporter)
(defrecord ReimportMe [a])
(ns importer)
(import exporter.ReimportMe)
(ReimportMe. 1))))
inst2 (binding [*ns* *ns*]
(eval (do (ns exporter)
(defrecord ReimportMe [a b])
(ns importer)
(import exporter.ReimportMe)
(ReimportMe. 1 2))))]
(testing "you can reimport a changed class and see the changes"
(is (= [:a] (keys inst1)))
(is (= [:a :b] (keys inst2))))
;fragile tests, please fix
#_(testing "you cannot import same local name from a different namespace"
(is (thrown? clojure.lang.Compiler$CompilerException
#"ReimportMe already refers to: class exporter.ReimportMe
in namespace: importer"
(binding [*ns* *ns*]
(eval (do (ns exporter-2)
(defrecord ReimportMe [a b])
(ns importer)
(import exporter-2.ReimportMe)
(ReimportMe. 1 2)))))))))
12.26. TEST/NUMBERS.CLJ 1607
(deftest naming-types
(testing
"you cannot use a name already referred from another namespace"
(is (thrown? IllegalStateException
#"String already refers to: class java.lang.String"
(definterface String)))
(is (thrown? IllegalStateException
#"StringBuffer already refers to: class java.lang.StringBuffer"
(deftype StringBuffer [])))
(is (thrown? IllegalStateException
#"Integer already refers to: class java.lang.Integer"
(defrecord Integer [])))))
(deftest resolution
(let [s (gensym)]
(are [result expr] (= result expr)
#clojure.core/first (ns-resolve clojure.core first)
nil (ns-resolve clojure.core s)
nil (ns-resolve clojure.core {first :local-first} first)
nil (ns-resolve clojure.core {first :local-first} s))))
(deftest refer-error-messages
(let [temp-ns (gensym)]
(binding [*ns* *ns*]
(in-ns temp-ns)
(eval (def ^{:private true} hidden-var)))
(testing "referring to something that does not exist"
(is (thrown-with-msg? IllegalAccessError
#"nonexistent-var does not exist"
(refer temp-ns :only (nonexistent-var)))))
(testing "referring to something non-public"
(is (thrown-with-msg? IllegalAccessError
#"hidden-var is not public"
(refer temp-ns :only (hidden-var)))))))
12.26 test/numbers.clj
test/numbers.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.numbers
(:use clojure.test
clojure.template))
; TODO:
; ==
; and more...
(deftest Coerced-BigDecimal
(let [v (bigdec 3)]
(are [x] (true? x)
(instance? BigDecimal v)
(number? v)
(decimal? v)
(not (float? v)))))
(deftest BigInteger-conversions
(are [x] (biginteger x)
Long/MAX_VALUE
13178456923875639284562345789M
13178456923875639284562345789N))
(deftest unchecked-cast-num-obj
(do-template [prim-array cast]
(are [n]
(let [a (prim-array 1)]
(aset a 0 (cast n)))
(Byte. Byte/MAX_VALUE)
(Short. Short/MAX_VALUE)
(Integer. Integer/MAX_VALUE)
(Long. Long/MAX_VALUE)
(Float. Float/MAX_VALUE)
(Double. Double/MAX_VALUE))
byte-array
unchecked-byte
short-array
unchecked-short
char-array
unchecked-char
int-array
unchecked-int
long-array
unchecked-long
float-array
12.26. TEST/NUMBERS.CLJ 1609
unchecked-float
double-array
unchecked-double))
(deftest unchecked-cast-num-prim
(do-template [prim-array cast]
(are [n]
(let [a (prim-array 1)]
(aset a 0 (cast n)))
Byte/MAX_VALUE
Short/MAX_VALUE
Integer/MAX_VALUE
Long/MAX_VALUE
Float/MAX_VALUE
Double/MAX_VALUE)
byte-array
unchecked-byte
short-array
unchecked-short
char-array
unchecked-char
int-array
unchecked-int
long-array
unchecked-long
float-array
unchecked-float
double-array
unchecked-double))
(deftest unchecked-cast-char
; in keeping with the checked cast functions, char and Character
; can only be cast to int
(is (unchecked-int (char 0xFFFF)))
(is (let [c (char 0xFFFF)] (unchecked-int c)))) ; force primitive char
(def expected-casts
[
[:input [-1 0 1
Byte/MAX_VALUE Short/MAX_VALUE Integer/MAX_VALUE Long/MAX_VALUE
Float/MAX_VALUE Double/MAX_VALUE]]
[char [:error (char 0) (char 1)
(char 127) (char 32767) :error :error
:error :error]]
[unchecked-char [(char 65535) (char 0) (char 1) (char 127)
(char 32767) (char 65535) (char 65535)
(char 65535) (char 65535)]]
[byte [-1 0 1 Byte/MAX_VALUE
:error :error :error :error
:error]]
1610 CHAPTER 12. TEST/CLOJURE
(deftest test-expected-casts
(let [[[_ inputs] & expectations] expected-casts]
(doseq [[f vals] expectations]
(let [wrapped (fn [x]
(try
(f x)
(catch IllegalArgumentException e :error)))]
(is (= vals (map wrapped inputs)))))))
(deftest test-add
(are [x y] (= x y)
12.26. TEST/NUMBERS.CLJ 1611
(+) 0
(+ 1) 1
(+ 1 2) 3
(+ 1 2 3) 6
(+ -1) -1
(+ -1 -2) -3
(+ -1 +2 -3) -2
(+ 1 -1) 0
(+ -1 1) 0
(+ 2/3) 2/3
(+ 2/3 1) 5/3
(+ 2/3 1/3) 1 )
; no overflow
(is (> (+ Integer/MAX_VALUE 10) Integer/MAX_VALUE))
; no string concatenation
(is (thrown? ClassCastException (+ "ab" "cd"))) )
(deftest test-subtract
(is (thrown? IllegalArgumentException (-)))
(are [x y] (= x y)
(- 1) -1
(- 1 2) -1
(- 1 2 3) -4
(- -2) 2
(- 1 -2) 3
(- 1 -2 -3) 6
(- 1 1) 0
(- -1 -1) 0
(- 2/3) -2/3
(- 2/3 1) -1/3
(- 2/3 1/3) 1/3 )
; no underflow
(is (< (- Integer/MIN_VALUE 10) Integer/MIN_VALUE)) )
(deftest test-multiply
(are [x y] (= x y)
(*) 1
(* 2) 2
(* 2 3) 6
(* 2 3 4) 24
(* -2) -2
(* 2 -3) -6
(* 2 -3 -1) 6
(* 1/2) 1/2
(* 1/2 1/3) 1/6
(* 1/2 1/3 -1/4) -1/24 )
; no overflow
(is (> (* 3 (int (/ Integer/MAX_VALUE 2.0))) Integer/MAX_VALUE)) )
(deftest test-ratios-simplify-to-ints-where-appropriate
(testing "negative denominator (assembla #275)"
(is (integer? (/ 1 -1/2)))
(is (integer? (/ 0 -1/2)))))
(deftest test-divide
(are [x y] (= x y)
(/ 1) 1
(/ 2) 1/2
(/ 3 2) 3/2
(/ 4 2) 2
(/ 24 3 2) 4
(/ 24 3 2 -1) -4
(/ -1) -1
(/ -2) -1/2
(/ -3 -2) 3/2
(/ -4 -2) 2
(/ -4 2) -2 )
;; mod
;; http://en.wikipedia.org/wiki/Modulo_operation
;; http://mathforum.org/library/drmath/view/52343.html
;;
;; is mod correct?
;; http://groups.google.com/group/clojure/
;; browse_frm/thread/2a0ee4d248f3d131#
;;
;; Issue 23: mod (modulo) operator
;; http://code.google.com/p/clojure/issues/detail?id=23
(deftest test-mod
; wrong number of args
; (is (thrown? IllegalArgumentException (mod)))
; (is (thrown? IllegalArgumentException (mod 1)))
; (is (thrown? IllegalArgumentException (mod 3 2 1)))
; divide by zero
(is (thrown? ArithmeticException (mod 9 0)))
(is (thrown? ArithmeticException (mod 0 0)))
(are [x y] (= x y)
(mod 4 2) 0
(mod 3 2) 1
(mod 6 4) 2
(mod 0 5) 0
(mod 2 1/2) 0
(mod 2/3 1/2) 1/6
(mod 1 2/3) 1/3
(mod 9 3) 0 ; (9 / 3) * 3 + (9 mod 3) = 3 * 3 + 0 = 9
(mod 9 -3) 0
(mod -9 3) 0
(mod -9 -3) 0
; num = 0, div != 0
(mod 0 3) 0 ; (0 / 3) * 3 + (0 mod 3) = 0 * 3 + 0 = 0
(mod 0 -3) 0
)
)
(deftest test-rem
; wrong number of args
; (is (thrown? IllegalArgumentException (rem)))
; (is (thrown? IllegalArgumentException (rem 1)))
; (is (thrown? IllegalArgumentException (rem 3 2 1)))
; divide by zero
(is (thrown? ArithmeticException (rem 9 0)))
(is (thrown? ArithmeticException (rem 0 0)))
(are [x y] (= x y)
(rem 4 2) 0
(rem 3 2) 1
(rem 6 4) 2
(rem 0 5) 0
(rem 2 1/2) 0
(rem 2/3 1/2) 1/6
(rem 1 2/3) 1/3
; num = 0, div != 0
(rem 0 3) 0
(rem 0 -3) 0
)
)
(deftest test-quot
; wrong number of args
; (is (thrown? IllegalArgumentException (quot)))
; (is (thrown? IllegalArgumentException (quot 1)))
; (is (thrown? IllegalArgumentException (quot 3 2 1)))
; divide by zero
(is (thrown? ArithmeticException (quot 9 0)))
(is (thrown? ArithmeticException (quot 0 0)))
(are [x y] (= x y)
(quot 4 2) 2
(quot 3 2) 1
(quot 6 4) 1
(quot 0 5) 0
(quot 2 1/2) 4
(quot 2/3 1/2) 1
(quot 1 2/3) 1
; num = 0, div != 0
(quot 0 3) 0
(quot 0 -3) 0
)
)
(deftest test-pos?-zero?-neg?
(let [nums [[(byte 2) (byte 0) (byte -2)]
[(short 3) (short 0) (short -3)]
[(int 4) (int 0) (int -4)]
[(long 5) (long 0) (long -5)]
[(bigint 6) (bigint 0) (bigint -6)]
[(float 7) (float 0) (float -7)]
[(double 8) (double 0) (double -8)]
[(bigdec 9) (bigdec 0) (bigdec -9)]
[2/3 0 -2/3]]
pred-result [[pos? [true false false]]
[zero? [false true false]]
[neg? [false false true]]] ]
(doseq [pr pred-result]
(doseq [n nums]
(is (= (map (first pr) n) (second pr))
(pr-str (first pr) n))))))
;; even? odd?
(deftest test-even?
(are [x] (true? x)
(even? -4)
(not (even? -3))
(even? 0)
(not (even? 5))
12.26. TEST/NUMBERS.CLJ 1617
(even? 8))
(is (thrown? ArithmeticException (even? 1/2)))
(is (thrown? ArithmeticException (even? (double 10)))))
(deftest test-odd?
(are [x] (true? x)
(not (odd? -4))
(odd? -3)
(not (odd? 0))
(odd? 5)
(not (odd? 8)))
(is (thrown? ArithmeticException (odd? 1/2)))
(is (thrown? ArithmeticException (odd? (double 10)))))
(defn- expt
"clojure.contrib.math/expt is a better and much faster impl,
but this works. Math/pow overflows to Infinity."
[x n] (apply * (replicate n x)))
(deftest test-bit-shift-left
(are [x y] (= x y)
2r10 (bit-shift-left 2r1 1)
2r100 (bit-shift-left 2r1 2)
2r1000 (bit-shift-left 2r1 3)
2r00101110 (bit-shift-left 2r00010111 1)
2r00101110 (apply bit-shift-left [2r00010111 1])
2r01 (bit-shift-left 2r10 -1)
(expt 2 32) (bit-shift-left 1 32)
(expt 2N 10000) (bit-shift-left 1N 10000)
))
(deftest test-bit-shift-right
(are [x y] (= x y)
2r0 (bit-shift-right 2r1 1)
2r010 (bit-shift-right 2r100 1)
2r001 (bit-shift-right 2r100 2)
2r000 (bit-shift-right 2r100 3)
2r0001011 (bit-shift-right 2r00010111 1)
2r0001011 (apply bit-shift-right [2r00010111 1])
2r100 (bit-shift-right 2r10 -1)
1 (bit-shift-right (expt 2 32) 32)
1N (bit-shift-right (expt 2N 10000) 10000)
))
;; arrays
(deftest test-array-types
(are [x y z] (= (Class/forName x) (class y) (class z))
"[Z" (boolean-array 1) (booleans (boolean-array 1 true))
"[B" (byte-array 1) (bytes (byte-array 1 (byte 1)))
1618 CHAPTER 12. TEST/CLOJURE
(deftest test-ratios
(is (== (denominator 1/2) 2))
(is (== (numerator 1/2) 1))
(is (= (bigint (/ 100000000000000000000 3)) 33333333333333333333))
(is (= (long 10000000000000000000/3) 3333333333333333333)))
12.27 test/otherfunctions.clj
test/otherfunctions.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.other-functions
(:use clojure.test))
; http://clojure.org/other_functions
(deftest test-identity
; exactly 1 argument needed
; (is (thrown? IllegalArgumentException (identity)))
; (is (thrown? IllegalArgumentException (identity 1 2)))
"" "abc"
sym
:kw
() (1 2)
[] [1 2]
{} {:a 1 :b 2}
#{} #{1 2} )
; evaluation
(are [x y] (= (identity x) y)
(+ 1 2) 3
(> 5 0) true ))
(deftest test-name
(are [x y] (= x (name y))
"foo" :foo
"bar" bar
"quux" "quux"))
(deftest test-fnil
(let [f1 (fnil vector :a)
f2 (fnil vector :a :b)
f3 (fnil vector :a :b :c)]
(are [result input]
(= result [(apply f1 input) (apply f2 input) (apply f3 input)])
[[1 2 3 4] [1 2 3 4] [1 2 3 4]] [1 2 3 4]
[[:a 2 3 4] [:a 2 3 4] [:a 2 3 4]] [nil 2 3 4]
[[:a nil 3 4] [:a :b 3 4] [:a :b 3 4]] [nil nil 3 4]
[[:a nil nil 4] [:a :b nil 4] [:a :b :c 4]] [nil nil nil 4]
[[:a nil nil nil] [:a :b nil nil]
[:a :b :c nil]] [nil nil nil nil]))
(are [x y] (= x y)
((fnil + 0) nil 42) 42
((fnil conj []) nil 42) [42]
(reduce #(update-in %1 [%2] (fnil inc 0)) {}
["fun" "counting" "words" "fun"])
{"words" 1, "counting" 1, "fun" 2}
(reduce #(update-in %1 [(first %2)] (fnil conj []) (second %2)) {}
[[:a 1] [:a 2] [:b 3]])
{:b [3], :a [1 2]}))
; partial
; comp
(deftest test-comp
(let [c0 (comp)]
(are [x] (= (identity x) (c0 x))
1620 CHAPTER 12. TEST/CLOJURE
nil
42
[1 2 3]
#{}
:foo)
(are [x y] (= (identity x) (c0 y))
(+ 1 2 3) 6
(keyword "foo") :foo)))
; complement
; constantly
; Printing
; pr prn print println newline
; pr-str prn-str print-str println-str [with-out-str (vars.clj)]
; Regex Support
; re-matcher re-find re-matches re-groups re-seq
12.28 test/parallel.clj
test/parallel.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.parallel
(:use clojure.test))
; future-call
; future
; pmap
; pcalls
; pvalues
;; pmap
;;
(deftest pmap-does-its-thing
12.29. TEST/PPRINT.CLJ 1621
12.29 test/pprint.clj
test/pprint.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.pprint
(:refer-clojure :exclude [format])
(:use [clojure.test :only (deftest are run-tests)]
[clojure.test-helper :only [platform-newlines]]
clojure.test-clojure.pprint.test-helper
clojure.pprint))
12.30 test/predicates.clj
test/predicates.clj
\getchunk{Clojure Copyright}
;;
;; Created 1/28/2009
(ns clojure.test-clojure.predicates
(:use clojure.test))
(def sample-data {
:nil nil
:bool-true true
:bool-false false
:byte (byte 7)
:short (short 7)
:int (int 7)
:long (long 7)
:bigint (bigint 7)
:float (float 7)
:double (double 7)
:bigdec (bigdec 7)
:ratio 2/3
:character \a
:symbol abc
:keyword :kw
:empty-string ""
:empty-regex #""
:empty-list ()
:empty-lazy-seq (lazy-seq nil)
:empty-vector []
:empty-map {}
:empty-set #{}
:empty-array (into-array [])
:string "abc"
:regex #"a*b"
:list (1 2 3)
:lazy-seq (lazy-seq [1 2 3])
:vector [1 2 3]
:map {:a 1 :b 2 :c 3}
:set #{1 2 3}
:array (into-array [1 2 3])
:class java.util.Date
:object (new java.util.Date)
(def type-preds {
nil? [:nil]
true? [:bool-true]
false? [:bool-false]
; boolean?
; character?
symbol? [:symbol]
keyword? [:keyword]
fn? [:fn]
ifn? [:fn
:empty-vector :vector :empty-map :map :empty-set :set
:keyword :symbol :var]
class? [:class]
var? [:var]
delay? [:delay]
})
(deftest test-type-preds
(doseq [tp type-preds]
(doseq [dt sample-data]
(if (some #(= % (first dt)) (second tp))
(is ((first tp) (second dt))
(pr-str (list (get-fn-name (first tp)) (second dt))))
(is (not ((first tp) (second dt)))
(pr-str
(list not
(list (get-fn-name (first tp)) (second dt)))))))))
;; Additional tests:
;; http://groups.google.com/group/clojure/browse_thread/
;; thread/537761a06edb4b06/bfd4f0705b746a38
;;
(deftest test-string?-more
(are [x] (not (string? x))
(new java.lang.StringBuilder "abc")
(new java.lang.StringBuffer "xyz")))
12.31 test/printer.clj
test/printer.clj
\getchunk{Clojure Copyright}
;; clojure.test-clojure.printer
;;
;; scgilardi (gmail)
;; Created 29 October 2008
(ns clojure.test-clojure.printer
(:use clojure.test))
(deftest print-length-empty-seq
(let [coll () val "()"]
12.31. TEST/PRINTER.CLJ 1625
(deftest print-length-seq
(let [coll (range 5)
length-val ((0 "(...)")
(1 "(0 ...)")
(2 "(0 1 ...)")
(3 "(0 1 2 ...)")
(4 "(0 1 2 3 ...)")
(5 "(0 1 2 3 4)"))]
(doseq [[length val] length-val]
(binding [*print-length* length]
(is (= val (print-str coll)))))))
(deftest print-length-empty-vec
(let [coll [] val "[]"]
(is (= val (binding [*print-length* 0] (print-str coll))))
(is (= val (binding [*print-length* 1] (print-str coll))))))
(deftest print-length-vec
(let [coll [0 1 2 3 4]
length-val ((0 "[...]")
(1 "[0 ...]")
(2 "[0 1 ...]")
(3 "[0 1 2 ...]")
(4 "[0 1 2 3 ...]")
(5 "[0 1 2 3 4]"))]
(doseq [[length val] length-val]
(binding [*print-length* length]
(is (= val (print-str coll)))))))
(deftest print-level-seq
(let [coll (0 (1 (2 (3 (4)))))
level-val ((0 "#")
(1 "(0 #)")
(2 "(0 (1 #))")
(3 "(0 (1 (2 #)))")
(4 "(0 (1 (2 (3 #))))")
(5 "(0 (1 (2 (3 (4)))))"))]
(doseq [[level val] level-val]
(binding [*print-level* level]
(is (= val (print-str coll)))))))
(deftest print-level-length-coll
(let [coll (if (member x y) (+ (first x) 3) (foo (a b c d "Baz")))
level-length-val
((0 1 "#")
(1 1 "(if ...)")
(1 2 "(if # ...)")
1626 CHAPTER 12. TEST/CLOJURE
(1 3 "(if # # ...)")
(1 4 "(if # # #)")
(2 1 "(if ...)")
(2 2 "(if (member x ...) ...)")
(2 3 "(if (member x y) (+ # 3) ...)")
(3 2 "(if (member x ...) ...)")
(3 3 "(if (member x y) (+ (first x) 3) ...)")
(3 4 "(if (member x y) (+ (first x) 3) (foo (a b c d ...)))")
(3 5 "(if (member x y) (+ (first x) 3) (foo (a b c d Baz)))"))]
(doseq [[level length val] level-length-val]
(binding [*print-level* level
*print-length* length]
(is (= val (print-str coll)))))))
12.32 test/protocols.clj
test/protocols.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.protocols
(:use clojure.test clojure.test-clojure.protocols.examples)
(:require [clojure.test-clojure.protocols.more-examples :as other]
[clojure.set :as set]
clojure.test-helper)
(:import [clojure.test_clojure.protocols.examples ExampleInterface]))
(defn method-names
"return sorted list of method names on a class"
12.32. TEST/PROTOCOLS.CLJ 1627
[c]
(->> (.getMethods c)
(map #(.getName %))
(sort)))
; clojure.test-clojure.protocols.examples/ExampleProtocol
; {:foo (fn [_] :extended)})))))
; (testing "you cannot extend to an interface"
; (is (fails-with-cause? IllegalArgumentException
; (str "interface "
; "clojure.test_clojure.protocols.examples.ExampleProtocol "
; "is not a protocol")
; (eval (extend clojure.test_clojure.protocols.HasProtocolInline
; clojure.test_clojure.protocols.examples.ExampleProtocol
; {:foo (fn [_] :extended)}))))))
(deftype ExtendsTestWidget []
ExampleProtocol)
#_(deftest extends?-test
(reload-example-protocols)
(testing
"returns false if a type does not implement the protocol at all"
(is (false? (extends? other/SimpleProtocol ExtendsTestWidget))))
;; semantics changed 4/15/2010
(testing "returns true if a type implements the protocol directly"
(is (true? (extends? ExampleProtocol ExtendsTestWidget))))
(testing "returns true if a type explicitly extends protocol"
(extend
ExtendsTestWidget
other/SimpleProtocol
{:foo identity})
(is (true? (extends? other/SimpleProtocol ExtendsTestWidget)))))
(deftype SatisfiesTestWidget []
ExampleProtocol)
#_(deftest satisifies?-test
(reload-example-protocols)
(let [whatzit (SatisfiesTestWidget.)]
(testing #"returns false if a type does not
implement the protocol at all"
1630 CHAPTER 12. TEST/CLOJURE
(deftest defrecord-acts-like-a-map
(let [rec (r 1 2)]
(is (.equals (r 1 3 {} {:c 4}) (merge rec {:b 3 :c 4})))
(is (.equals {:foo 1 :b 2} (set/rename-keys rec {:a :foo})))
(is (.equals {:a 11 :b 2 :c 10} (merge-with + rec {:a 10 :c 10})))))
12.32. TEST/PROTOCOLS.CLJ 1631
(deftest degenerate-defrecord-test
(let [empty (EmptyRecord.)]
(is (nil? (seq empty)))
(is (not (.containsValue empty :a)))))
(deftest defrecord-interfaces-test
(testing "java.util.Map"
(let [rec (r 1 2)]
(is (= 2 (.size rec)))
(is (= 3 (.size (assoc rec :c 3))))
(is (not (.isEmpty rec)))
(is (.isEmpty (EmptyRecord.)))
(is (.containsKey rec :a))
(is (not (.containsKey rec :c)))
(is (.containsValue rec 1))
(is (not (.containsValue rec 3)))
(is (= 1 (.get rec :a)))
(is (thrown? UnsupportedOperationException (.put rec :a 1)))
(is (thrown? UnsupportedOperationException (.remove rec :a)))
(is (thrown? UnsupportedOperationException (.putAll rec {})))
(is (thrown? UnsupportedOperationException (.clear rec)))
(is (= #{:a :b} (.keySet rec)))
(is (= #{1 2} (set (.values rec))))
(is (= #{[:a 1] [:b 2]} (.entrySet rec)))
))
(testing "IPersistentCollection"
(testing ".cons"
(let [rec (r 1 2)]
(are [x] (= rec (.cons rec x))
nil {})
(is (= (r 1 3) (.cons rec {:b 3})))
(is (= (r 1 4) (.cons rec [:b 4])))
(is (= (r 1 5) (.cons rec (MapEntry. :b 5))))))))
(deftest reify-test
(testing "of an interface"
(let [s :foo
r (reify
java.util.List
(contains [_ o] (= s o)))]
1632 CHAPTER 12. TEST/CLOJURE
12.33 test/reader.clj
test/reader.clj
\getchunk{Clojure Copyright}
;;
;; Tests for the Clojure functions documented at the URL:
;;
;; http://clojure.org/Reader
;;
;; scgilardi (gmail)
;; Created 22 October 2008
(ns clojure.test-clojure.reader
(:use clojure.test)
(:import clojure.lang.BigInt))
;; Symbols
(deftest Symbols
(is (= abc (symbol "abc")))
(is (= *+!-_? (symbol "*+!-_?")))
(is (= abc:def:ghi (symbol "abc:def:ghi")))
1634 CHAPTER 12. TEST/CLOJURE
;; Literals
(deftest Literals
; nil false true are reserved by Clojure and are not symbols
(is (= nil nil))
(is (= false false))
(is (= true true)) )
;; Strings
(deftest Strings
(is (= "abcde" (str \a \b \c \d \e)))
(is (= "abc
def" (str \a \b \c \newline \space \space \d \e \f)))
)
;; Numbers
(deftest Numbers
; Read Integer
(is (instance? Long 2147483647))
(is (instance? Long +1))
(is (instance? Long 1))
(is (instance? Long +0))
(is (instance? Long 0))
(is (instance? Long -0))
(is (instance? Long -1))
(is (instance? Long -2147483648))
; Read Long
(is (instance? Long 2147483648))
(is (instance? Long -2147483649))
(is (instance? Long 9223372036854775807))
(is (instance? Long -9223372036854775808))
l))]
(is (= [4 3 2 1 0] sequence))
(is (every? #(instance? Long %)
sequence))))
; Read BigInteger
(is (instance? BigInt 9223372036854775808))
(is (instance? BigInt -9223372036854775809))
(is (instance? BigInt
10000000000000000000000000000000000000000000000000))
(is (instance? BigInt
-10000000000000000000000000000000000000000000000000))
; Read Double
(is (instance? Double +1.0e+1))
(is (instance? Double +1.e+1))
(is (instance? Double +1e+1))
; Read BigDecimal
(is (instance? BigDecimal 9223372036854775808M))
(is (instance? BigDecimal -9223372036854775809M))
(is (instance? BigDecimal 2147483647M))
(is (instance? BigDecimal +1M))
(is (instance? BigDecimal 1M))
(is (instance? BigDecimal +0M))
(is (instance? BigDecimal 0M))
(is (instance? BigDecimal -0M))
(is (instance? BigDecimal -1M))
(is (instance? BigDecimal -2147483648M))
;; Characters
(deftest t-Characters)
;; nil
(deftest t-nil)
;; Booleans
(deftest t-Booleans)
;; Keywords
(deftest t-Keywords
1638 CHAPTER 12. TEST/CLOJURE
(deftest reading-keywords
(are [x y] (= x (read-string y))
:foo ":foo"
:foo/bar ":foo/bar"
:user/foo "::foo")
(are [err msg form] (thrown-with-msg? err msg (read-string form))
Exception #"Invalid token: foo:" "foo:"
Exception #"Invalid token: :bar/" ":bar/"
Exception #"Invalid token: ::does.not/exist" "::does.not/exist"))
;; Lists
(deftest t-Lists)
;; Vectors
(deftest t-Vectors)
;; Maps
(deftest t-Maps)
;; Sets
(deftest t-Sets)
;; Macro characters
;; Quote ()
(deftest t-Quote)
;; Character (\)
(deftest t-Character)
;; Comment (;)
(deftest t-Comment)
12.33. TEST/READER.CLJ 1639
;; Deref (@)
(deftest t-Deref)
;; Dispatch (#)
(deftest t-Regex)
;; Metadata (^ or #^ (deprecated))
(deftest t-Metadata
(is (= (meta ^:static ^:awesome ^{:static false :bar :baz} sym)
{:awesome true, :bar :baz, :static true})))
;; Var-quote (#)
(deftest t-Var-quote)
(deftest t-Anonymouns-function-literal)
(deftest t-Syntax-quote
(are [x y] (= x y)
() () ; was NPE before SVN r1337
))
;; (read)
;; (read stream)
;; (read stream eof-is-error)
;; (read stream eof-is-error eof-value)
;; (read stream eof-is-error eof-value is-recursive)
(deftest t-read)
-
1640 CHAPTER 12. TEST/CLOJURE
12.34 test/reflect.clj
test/reflect.clj
(ns clojure.test-clojure.reflect
(:use clojure.data [clojure.reflect
:as reflect] clojure.test clojure.pprint)
(:import [clojure.reflect AsmReflector JavaReflector]))
(defn nodiff
[x y]
(let [[x-only y-only common] (diff x y)]
(when (or x-only y-only)
(is false (with-out-str (pprint {:x-only x-only
:y-only y-only
:common common}))))))
(deftest compare-reflect-and-asm
(let [cl (.getContextClassLoader (Thread/currentThread))
asm-reflector (AsmReflector. cl)
java-reflector (JavaReflector. cl)]
(doseq [classname [java.lang.Runnable
java.lang.Object
java.io.FileInputStream
clojure.lang.Compiler
clojure.lang.PersistentVector]]
(nodiff (type-reflect classname :reflector asm-reflector)
(type-reflect classname :reflector java-reflector)))))
(deftest field-descriptor->class-symbol-test
(are [s d] (= s (@#reflect/field-descriptor->class-symbol d))
clojure.asm.Type<><> "[[Lclojure/asm/Type;"
int "I"
java.lang.Object "Ljava.lang.Object;"))
(deftest internal-name->class-symbol-test
(are [s n] (= s (@#reflect/internal-name->class-symbol n))
java.lang.Exception "java/lang/Exception"))
12.35 test/refs.clj
test/refs.clj
\getchunk{Clojure Copyright}
12.36. TEST/REPL.CLJ 1641
(ns clojure.test-clojure.refs
(:use clojure.test))
; http://clojure.org/refs
; ref
; deref, @-reader-macro
; dosync io!
; ensure ref-set alter commute
; set-validator get-validator
12.36 test/repl.clj
test/repl.clj
(ns clojure.test-clojure.repl
(:use clojure.test
clojure.repl
[clojure.test-helper :only [platform-newlines]]
clojure.test-clojure.repl.example))
(deftest test-source
(is (= "(defn foo [])"
(source-fn clojure.test-clojure.repl.example/foo)))
(is (= (platform-newlines "(defn foo [])\n")
(with-out-str (source clojure.test-clojure.repl.example/foo))))
(is (nil? (source-fn non-existent-fn))))
(deftest test-dir
(is (thrown? Exception (dir-fn non-existent-ns)))
(is (= [bar foo] (dir-fn clojure.test-clojure.repl.example)))
(is (= (platform-newlines "bar\nfoo\n")
(with-out-str (dir clojure.test-clojure.repl.example)))))
(deftest test-apropos
(testing "with a regular expression"
(is (= [defmacro] (apropos #"^defmacro$")))
(is (some #{defmacro} (apropos #"def.acr.")))
(is (= [] (apropos #"nothing-has-this-name"))))
1642 CHAPTER 12. TEST/CLOJURE
12.37 test/rt.clj
test/rt.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.rt
(:use clojure.test clojure.test-helper))
(defmacro with-err-print-writer
"Evaluate with err pointing to a temporary PrintWriter, and
return err contents as a string."
[& body]
(let [s# (java.io.StringWriter.)
p# (java.io.PrintWriter. s#)]
(binding [*err* p#]
~@body
(str s#))))
(defmacro with-err-string-writer
"Evaluate with err pointing to a temporary StringWriter, and
return err contents as a string."
[& body]
(let [s# (java.io.StringWriter.)]
(binding [*err* s#]
~@body
(str s#))))
(defmacro should-print-err-message
"Turn on all warning flags, and test that error message prints
correctly for all semi-reasonable bindings of *err*."
[msg-re form]
(binding [*warn-on-reflection* true]
12.37. TEST/RT.CLJ 1643
(defn bare-rt-print
"Return string RT would print prior to print-initialize"
[x]
(with-out-str
(try
(push-thread-bindings {#clojure.core/print-initialized false})
(clojure.lang.RT/print x *out*)
(finally
(pop-thread-bindings)))))
(deftest rt-print-prior-to-print-initialize
(testing "pattern literals"
(is (= "#\"foo\"" (bare-rt-print #"foo")))))
(def example-var)
(deftest binding-root-clears-macro-metadata
1644 CHAPTER 12. TEST/CLOJURE
(deftest last-var-wins-for-core
(testing "you can replace a core name, with warning"
(let [ns (temp-ns)
replacement (gensym)]
(with-err-string-writer (intern ns prefers replacement))
(is (= replacement @(prefers (ns-publics ns))))))
(testing "you can replace a name you defined before"
(let [ns (temp-ns)
s (gensym)
v1 (intern ns foo s)
v2 (intern ns bar s)]
(with-err-string-writer (.refer ns flatten v1))
(.refer ns flatten v2)
(is (= v2 (ns-resolve ns flatten)))))
(testing "you cannot intern over an existing non-core name"
(let [ns (temp-ns clojure.set)
replacement (gensym)]
(is (thrown? IllegalStateException
(intern ns subset? replacement)))
(is (nil? (subset? (ns-publics ns))))
(is (= #clojure.set/subset? (subset? (ns-refers ns))))))
(testing "you cannot refer over an existing non-core name"
(let [ns (temp-ns clojure.set)
replacement (gensym)]
(is (thrown? IllegalStateException
(.refer ns subset? #clojure.set/intersection)))
(is (nil? (subset? (ns-publics ns))))
(is (= #clojure.set/subset? (subset? (ns-refers ns)))))))
12.38 test/sequences.clj
test/sequences.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.sequences
(:use clojure.test))
12.38. TEST/SEQUENCES.CLJ 1645
; TODO:
; apply, map, filter, remove
; and more...
(deftest test-reduce-from-chunked-into-unchunked
(= [1 2 \a \b] (into [] (concat [1 2] "ab"))))
(deftest test-reduce
(let [int+ (fn [a b] (+ (int a) (int b)))
arange (range 100) ;; enough to cross nodes
avec (into [] arange)
alist (into () arange)
obj-array (into-array arange)
int-array
(into-array Integer/TYPE (map #(Integer. (int %)) arange))
long-array (into-array Long/TYPE arange)
float-array (into-array Float/TYPE arange)
char-array (into-array Character/TYPE (map char arange))
double-array (into-array Double/TYPE arange)
byte-array (into-array Byte/TYPE (map byte arange))
int-vec (into (vector-of :int) arange)
long-vec (into (vector-of :long) arange)
float-vec (into (vector-of :float) arange)
char-vec (into (vector-of :char) (map char arange))
double-vec (into (vector-of :double) arange)
byte-vec (into (vector-of :byte) (map byte arange))
all-true (into-array Boolean/TYPE (repeat 10 true))]
(is (== 4950
(reduce + arange)
(reduce + avec)
(reduce + alist)
(reduce + obj-array)
(reduce + int-array)
(reduce + long-array)
(reduce + float-array)
(reduce int+ char-array)
(reduce + double-array)
(reduce int+ byte-array)
(reduce + int-vec)
(reduce + long-vec)
(reduce + float-vec)
(reduce int+ char-vec)
(reduce + double-vec)
(reduce int+ byte-vec)))
(is (== 4951
(reduce + 1 arange)
(reduce + 1 avec)
1646 CHAPTER 12. TEST/CLOJURE
(reduce + 1 alist)
(reduce + 1 obj-array)
(reduce + 1 int-array)
(reduce + 1 long-array)
(reduce + 1 float-array)
(reduce int+ 1 char-array)
(reduce + 1 double-array)
(reduce int+ 1 byte-array)
(reduce + 1 int-vec)
(reduce + 1 long-vec)
(reduce + 1 float-vec)
(reduce int+ 1 char-vec)
(reduce + 1 double-vec)
(reduce int+ 1 byte-vec)))
(is (= true
(reduce #(and %1 %2) all-true)
(reduce #(and %1 %2) true all-true)))))
(deftest test-equality
; lazy sequences
(are [x y] (= x y)
; fixed SVN 1288 - LazySeq and EmptyList equals/equiv
; http://groups.google.com/group/clojure/
; browse_frm/thread/286d807be9cae2a5#
(map inc nil) ()
(map inc ()) ()
(map inc []) ()
(map inc #{}) ()
(map inc {}) () ))
(deftest test-lazy-seq
(are [x] (seq? x)
(lazy-seq nil)
(lazy-seq [])
(lazy-seq [1 2]))
(are [x y] (= x y)
(lazy-seq nil) ()
(lazy-seq [nil]) (nil)
(lazy-seq ()) ()
(lazy-seq []) ()
(lazy-seq #{}) ()
(lazy-seq {}) ()
(lazy-seq "") ()
(lazy-seq (into-array [])) ()
(deftest test-seq
(is (not (seq? (seq []))))
(is (seq? (seq [1 2])))
(are [x y] (= x y)
(seq nil) nil
(seq [nil]) (nil)
(deftest test-cons
(is (thrown? IllegalArgumentException (cons 1 2)))
(are [x y] (= x y)
(cons 1 nil) (1)
(cons nil nil) (nil)
(deftest test-empty
(are [x y] (and (= (empty x) y)
(= (class (empty x)) (class y)))
nil nil
() ()
(1 2) ()
[] []
[1 2] []
{} {}
{:a 1 :b 2} {}
(sorted-map) (sorted-map)
(sorted-map :a 1 :b 2) (sorted-map)
#{} #{}
#{1 2} #{}
(sorted-set) (sorted-set)
(sorted-set 1 2) (sorted-set)
(lazy-seq ()) ()
(lazy-seq (1 2)) ()
(lazy-seq []) ()
(lazy-seq [1 2]) ()
(first y))
(sorted-set 1 2 3) (sorted-set 1 2 3)
(sorted-set-by inv-compare 1 2 3) (sorted-set-by inv-compare 1 2 3)
(deftest test-not-empty
; empty coll/seq => nil
(are [x] (= (not-empty x) nil)
()
[]
{}
#{}
(seq ())
(seq [])
(lazy-seq ())
(lazy-seq []) )
(deftest test-first
;(is (thrown? Exception (first)))
(is (thrown? IllegalArgumentException (first true)))
(is (thrown? IllegalArgumentException (first false)))
(is (thrown? IllegalArgumentException (first 1)))
;(is (thrown? IllegalArgumentException (first 1 2)))
(is (thrown? IllegalArgumentException (first \a)))
(is (thrown? IllegalArgumentException (first s)))
(is (thrown? IllegalArgumentException (first :k)))
(are [x y] (= x y)
(first nil) nil
; string
(first "") nil
(first "a") \a
(first "abc") \a
1650 CHAPTER 12. TEST/CLOJURE
; list
(first ()) nil
(first (1)) 1
(first (1 2 3)) 1
; vector
(first []) nil
(first [1]) 1
(first [1 2 3]) 1
; set
(first #{}) nil
(first #{1}) 1
(first (sorted-set 1 2 3)) 1
; map
(first {}) nil
(first (sorted-map :a 1)) (:a 1)
(first (sorted-map :a 1 :b 2 :c 3)) (:a 1)
; array
(first (into-array [])) nil
(first (into-array [1])) 1
(first (into-array [1 2 3])) 1
(first (to-array [nil])) nil
(first (to-array [1 nil])) 1
(first (to-array [nil 2])) nil ))
12.38. TEST/SEQUENCES.CLJ 1651
(deftest test-next
; (is (thrown? IllegalArgumentException (next)))
(is (thrown? IllegalArgumentException (next true)))
(is (thrown? IllegalArgumentException (next false)))
(is (thrown? IllegalArgumentException (next 1)))
;(is (thrown? IllegalArgumentException (next 1 2)))
(is (thrown? IllegalArgumentException (next \a)))
(is (thrown? IllegalArgumentException (next s)))
(is (thrown? IllegalArgumentException (next :k)))
(are [x y] (= x y)
(next nil) nil
; string
(next "") nil
(next "a") nil
(next "abc") (\b \c)
; list
(next ()) nil
(next (1)) nil
(next (1 2 3)) (2 3)
; vector
(next []) nil
(next [1]) nil
(next [1 2 3]) [2 3]
; set
(next #{}) nil
(next #{1}) nil
(next (sorted-set 1 2 3)) (2 3)
; map
(next {}) nil
(next (sorted-map :a 1)) nil
(next (sorted-map :a 1 :b 2 :c 3)) ((:b 2) (:c 3))
; array
(next (into-array [])) nil
(next (into-array [1])) nil
(next (into-array [1 2 3])) (2 3)
(deftest test-last
(are [x y] (= x y)
(last nil) nil
; list
(last ()) nil
(last (1)) 1
(last (1 2 3)) 3
; vector
(last []) nil
(last [1]) 1
(last [1 2 3]) 3
; set
(last #{}) nil
(last #{1}) 1
(last (sorted-set 1 2 3)) 3
; map
(last {}) nil
(last (sorted-map :a 1)) [:a 1]
(last (sorted-map :a 1 :b 2 :c 3)) [:c 3]
; string
(last "") nil
(last "a") \a
(last "abc") \c
; array
(last (into-array [])) nil
(last (into-array [1])) 1
(last (into-array [1 2 3])) 3
(last (to-array [nil])) nil
(last (to-array [1 nil])) nil
(last (to-array [nil 2])) 2 ))
(deftest test-nnext
; (is (thrown? IllegalArgumentException (nnext)))
(are [x y] (= x y)
(nnext nil) nil
(deftest test-nth
; maps, sets are not supported
(is (thrown? UnsupportedOperationException (nth {} 0)))
(is (thrown? UnsupportedOperationException (nth {:a 1 :b 2} 0)))
(is (thrown? UnsupportedOperationException (nth #{} 0)))
(is (thrown? UnsupportedOperationException (nth #{1 2 3} 0)))
; out of bounds
(is (thrown? IndexOutOfBoundsException (nth () 0)))
(is (thrown? IndexOutOfBoundsException (nth (1 2 3) 5)))
(is (thrown? IndexOutOfBoundsException (nth () -1)))
(is (thrown? IndexOutOfBoundsException (nth (1 2 3) -1)))
(are [x y] (= x y)
(nth (1) 0) 1
(nth (1 2 3) 0) 1
(nth (1 2 3 4 5) 1) 2
(nth (1 2 3 4 5) 4) 5
(nth (1 2 3) 5 :not-found) :not-found
(nth [1] 0) 1
(nth [1 2 3] 0) 1
(nth [1 2 3 4 5] 1) 2
(nth [1 2 3 4 5] 4) 5
(nth [1 2 3] 5 :not-found) :not-found
(nth "a" 0) \a
(nth "abc" 0) \a
(nth "abcde" 1) \b
(nth "abcde" 4) \e
(nth "abc" 5 :not-found) :not-found
; regex Matchers
(let [m (re-matcher #"(a)(b)" "ababaa")]
(re-find m) ; => ["ab" "a" "b"]
(are [x y] (= x y)
(nth m 0) "ab"
(nth m 1) "a"
12.38. TEST/SEQUENCES.CLJ 1657
(nth m 2) "b"
(nth m 3 :not-found) :not-found
(nth m -1 :not-found) :not-found )
(is (thrown? IndexOutOfBoundsException (nth m 3)))
(is (thrown? IndexOutOfBoundsException (nth m -1))))
(distinct []) ()
(distinct [1]) (1)
(distinct [1 2 3]) (1 2 3)
(distinct [1 2 3 1 2 2 1 1]) (1 2 3)
(distinct [1 1 1 2]) (1 2)
(distinct [1 2 1 2]) (1 2)
(distinct "") ()
(distinct "a") (\a)
(distinct "abc") (\a \b \c)
(distinct "abcabab") (\a \b \c)
(distinct "aaab") (\a \b)
(distinct "abab") (\a \b) )
0M 1M
\c
"" "abc"
sym
:kw
() (1 2)
[] [1 2]
{} {:a 1 :b 2}
#{} #{1 2} ))
(deftest test-interpose
(are [x y] (= x y)
(interpose 0 []) ()
(interpose 0 [1]) (1)
(interpose 0 [1 2]) (1 0 2)
(interpose 0 [1 2 3]) (1 0 2 0 3) ))
(deftest test-interleave
(are [x y] (= x y)
(interleave [1 2] [3 4]) (1 3 2 4)
(interleave [] [3 4]) ()
(interleave [1 2] []) ()
(interleave [] []) () ))
(deftest test-zipmap
(are [x y] (= x y)
(zipmap [:a :b] [1 2]) {:a 1 :b 2}
(zipmap [] [1 2]) {}
(zipmap [:a :b] []) {}
(zipmap [] []) {} ))
(deftest test-concat
(are [x y] (= x y)
(concat) ()
(concat []) ()
(concat [1 2]) (1 2)
12.38. TEST/SEQUENCES.CLJ 1659
(concat [1 2] [3 4]) (1 2 3 4)
(concat [] [3 4]) (3 4)
(concat [1 2] []) (1 2)
(concat [] []) ()
(concat [1 2] [3 4] [5 6]) (1 2 3 4 5 6) ))
(deftest test-cycle
(are [x y] (= x y)
(cycle []) ()
(deftest test-partition
(are [x y] (= x y)
(partition 2 [1 2 3]) ((1 2))
(partition 2 [1 2 3 4]) ((1 2) (3 4))
(partition 2 []) ()
(partition 1 []) ()
(partition 1 [1 2 3]) ((1) (2) (3))
(partition 5 [1 2 3]) ()
(deftest test-reverse
(are [x y] (= x y)
(reverse nil) () ; since SVN 1294
(reverse []) ()
(reverse [1]) (1)
(reverse [1 2 3]) (3 2 1) ))
(deftest test-take
(are [x y] (= x y)
(take 1 [1 2 3 4 5]) (1)
(take 3 [1 2 3 4 5]) (1 2 3)
1660 CHAPTER 12. TEST/CLOJURE
(take 5 [1 2 3 4 5]) (1 2 3 4 5)
(take 9 [1 2 3 4 5]) (1 2 3 4 5)
(take 0 [1 2 3 4 5]) ()
(take -1 [1 2 3 4 5]) ()
(take -2 [1 2 3 4 5]) () ))
(deftest test-drop
(are [x y] (= x y)
(drop 1 [1 2 3 4 5]) (2 3 4 5)
(drop 3 [1 2 3 4 5]) (4 5)
(drop 5 [1 2 3 4 5]) ()
(drop 9 [1 2 3 4 5]) ()
(drop 0 [1 2 3 4 5]) (1 2 3 4 5)
(drop -1 [1 2 3 4 5]) (1 2 3 4 5)
(drop -2 [1 2 3 4 5]) (1 2 3 4 5) ))
(deftest test-take-nth
(are [x y] (= x y)
(take-nth 1 [1 2 3 4 5]) (1 2 3 4 5)
(take-nth 2 [1 2 3 4 5]) (1 3 5)
(take-nth 3 [1 2 3 4 5]) (1 4)
(take-nth 4 [1 2 3 4 5]) (1 5)
(take-nth 5 [1 2 3 4 5]) (1)
(take-nth 9 [1 2 3 4 5]) (1)
(deftest test-take-while
(are [x y] (= x y)
(take-while pos? []) ()
(take-while pos? [1 2 3 4]) (1 2 3 4)
(take-while pos? [1 2 3 -1]) (1 2 3)
(take-while pos? [1 -1 2 3]) (1)
(take-while pos? [-1 1 2 3]) ()
(take-while pos? [-1 -2 -3]) () ))
(deftest test-drop-while
(are [x y] (= x y)
(drop-while pos? []) ()
(drop-while pos? [1 2 3 4]) ()
12.38. TEST/SEQUENCES.CLJ 1661
(deftest test-butlast
(are [x y] (= x y)
(butlast []) nil
(butlast [1]) nil
(butlast [1 2 3]) (1 2) ))
(deftest test-drop-last
(are [x y] (= x y)
; as butlast
(drop-last []) ()
(drop-last [1]) ()
(drop-last [1 2 3]) (1 2)
(drop-last 2 []) ()
(drop-last 2 [1]) ()
(drop-last 2 [1 2 3]) (1)
(drop-last 5 []) ()
(drop-last 5 [1]) ()
(drop-last 5 [1 2 3]) ()
(drop-last 0 []) ()
(drop-last 0 [1]) (1)
(drop-last 0 [1 2 3]) (1 2 3)
(drop-last -1 []) ()
(drop-last -1 [1]) (1)
(drop-last -1 [1 2 3]) (1 2 3)
(drop-last -2 []) ()
(drop-last -2 [1]) (1)
(drop-last -2 [1 2 3]) (1 2 3) ))
(deftest test-split-at
(is (vector? (split-at 2 [])))
(is (vector? (split-at 2 [1 2 3])))
1662 CHAPTER 12. TEST/CLOJURE
(are [x y] (= x y)
(split-at 2 []) [() ()]
(split-at 2 [1 2 3 4 5]) [(list 1 2) (list 3 4 5)]
(deftest test-split-with
(is (vector? (split-with pos? [])))
(is (vector? (split-with pos? [1 2 -1 0 3 4])))
(are [x y] (= x y)
(split-with pos? []) [() ()]
(split-with pos? [1 2 -1 0 3 4]) [(list 1 2) (list -1 0 3 4)]
(deftest test-repeat
;(is (thrown? IllegalArgumentException (repeat)))
; limited sequence
(are [x y] (= x y)
(repeat 0 7) ()
(repeat 1 7) (7)
(repeat 2 7) (7 7)
(repeat 5 7) (7 7 7 7 7)
(repeat -1 7) ()
(repeat -3 7) () )
0M 1M
\c
"" "abc"
sym
:kw
() (1 2)
[] [1 2]
{} {:a 1 :b 2}
#{} #{1 2} ))
(deftest test-range
(are [x y] (= x y)
(range 0) () ; exclusive end!
(range 1) (0)
(range 5) (0 1 2 3 4)
(range -1) ()
(range -3) ()
(range 2.5) (0 1 2)
(range 7/3) (0 1 2)
(range 0 3) (0 1 2)
(range 0 1) (0)
(range 0 0) ()
(range 0 -3) ()
(range 3 6) (3 4 5)
(range 3 4) (3)
(range 3 3) ()
(range 3 1) ()
(range 3 0) ()
(range 3 -2) ()
(range -2 5) (-2 -1 0 1 2 3 4)
(range -2 0) (-2 -1)
(range -2 -1) (-2)
(range -2 -2) ()
(range -2 -5) ()
(range 3 9 0) ()
(range 3 9 1) (3 4 5 6 7 8)
(range 3 9 2) (3 5 7)
(range 3 9 3) (3 6)
(range 3 9 10) (3)
(range 3 9 -1) () ))
(deftest test-empty?
1664 CHAPTER 12. TEST/CLOJURE
(deftest test-every?
; always true for nil or empty coll/seq
(are [x] (= (every? pos? x) true)
nil
() [] {} #{}
(lazy-seq [])
(into-array []) )
(are [x y] (= x y)
true (every? pos? [1])
true (every? pos? [1 2])
true (every? pos? [1 2 3 4 5])
(are [x y] (= x y)
true (every? #{:a} [:a :a])
;! false (every? #{:a} [:a :b]) ; Issue 68: every? returns
; nil instead of false
; http://code.google.com/p/clojure/issues/detail?id=68
;! false (every? #{:a} [:b :b])
))
12.38. TEST/SEQUENCES.CLJ 1665
(deftest test-not-every?
; always false for nil or empty coll/seq
(are [x] (= (not-every? pos? x) false)
nil
() [] {} #{}
(lazy-seq [])
(into-array []) )
(are [x y] (= x y)
false (not-every? pos? [1])
false (not-every? pos? [1 2])
false (not-every? pos? [1 2 3 4 5])
(are [x y] (= x y)
false (not-every? #{:a} [:a :a])
true (not-every? #{:a} [:a :b])
true (not-every? #{:a} [:b :b]) ))
(deftest test-not-any?
; always true for nil or empty coll/seq
(are [x] (= (not-any? pos? x) true)
nil
() [] {} #{}
(lazy-seq [])
(into-array []) )
(are [x y] (= x y)
false (not-any? pos? [1])
false (not-any? pos? [1 2])
false (not-any? pos? [1 2 3 4 5])
(are [x y] (= x y)
1666 CHAPTER 12. TEST/CLOJURE
(deftest test-some
;; always nil for nil or empty coll/seq
(are [x] (= (some pos? x) nil)
nil
() [] {} #{}
(lazy-seq [])
(into-array []))
(are [x y] (= x y)
nil (some nil nil)
(deftest test-flatten-present
(are [expected nested-val] (= (flatten nested-val) expected)
;simple literals
[] nil
[] 1
[] test
[] :keyword
[] 1/2
[] #"[\r\n]"
[] true
[] false
;vectors
[1 2 3 4 5] [[1 2] [3 4 [5]]]
[1 2 3 4 5] [1 2 3 4 5]
[#{1 2} 3 4 5] [#{1 2} 3 4 5]
;sets
[] #{}
[] #{#{1 2} 3 4 5}
12.38. TEST/SEQUENCES.CLJ 1667
[] #{1 2 3 4 5}
[] #{#{1 2} 3 4 5}
;lists
[] ()
[1 2 3 4 5] (1 2 3 4 5)
;maps
[] {:a 1 :b 2}
[:a 1 :b 2] (seq {:a 1 :b 2})
[] {[:a :b] 1 :c 2}
[:a :b 1 :c 2] (seq {[:a :b] 1 :c 2})
[:a 1 2 :b 3] (seq {:a [1 2] :b 3})
;Strings
[] "12345"
[\1 \2 \3 \4 \5] (seq "12345")
;fns
[] count
[count even? odd?] [count even? odd?]))
(deftest test-group-by
(is (= (group-by even? [1 2 3 4 5])
{false [1 3 5], true [2 4]})))
(deftest test-partition-by
(are [test-seq] (= (partition-by (comp even? count) test-seq)
[["a"] ["bb" "cccc" "dd"] ["eee" "f"] ["" "hh"]])
["a" "bb" "cccc" "dd" "eee" "f" "" "hh"]
("a" "bb" "cccc" "dd" "eee" "f" "" "hh"))
(is (=(partition-by #{\a \e \i \o \u} "abcdefghijklm")
[[\a] [\b \c \d] [\e] [\f \g \h] [\i] [\j \k \l \m]])))
(deftest test-frequencies
(are [expected test-seq] (= (frequencies test-seq) expected)
{\p 2, \s 4, \i 4, \m 1} "mississippi"
{1 4 2 2 3 1} [1 1 1 1 2 2 3]
{1 4 2 2 3 1} (1 1 1 1 2 2 3)))
(deftest test-reductions
(is (= (reductions + nil)
[0]))
(is (= (reductions + [1 2 3 4 5])
[1 3 6 10 15]))
(is (= (reductions + 10 [1 2 3 4 5])
[10 11 13 16 20 25])))
(deftest test-rand-nth-invariants
(let [elt (rand-nth [:a :b :c :d])]
(is (#{:a :b :c :d} elt))))
(deftest test-partition-all
(is (= (partition-all 4 [1 2 3 4 5 6 7 8 9])
1668 CHAPTER 12. TEST/CLOJURE
[[1 2 3 4] [5 6 7 8] [9]]))
(is (= (partition-all 4 2 [1 2 3 4 5 6 7 8 9])
[[1 2 3 4] [3 4 5 6] [5 6 7 8] [7 8 9] [9]])))
(deftest test-shuffle-invariants
(is (= (count (shuffle [1 2 3 4])) 4))
(let [shuffled-seq (shuffle [1 2 3 4])]
(is (every? #{1 2 3 4} shuffled-seq))))
12.39 test/serialization.clj
test/serialization.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.serialization
(:use clojure.test)
(:import (java.io ObjectOutputStream ObjectInputStream
ByteArrayOutputStream ByteArrayInputStream)))
(defn- serialize
"Serializes a single object, returning a byte array."
[v]
(with-open [bout (ByteArrayOutputStream.)
oos (ObjectOutputStream. bout)]
(.writeObject oos v)
(.flush oos)
(.toByteArray bout)))
(defn- deserialize
"Deserializes and returns a single object from the given byte array."
[bytes]
(with-open [ois (-> bytes ByteArrayInputStream. ObjectInputStream.)]
(.readObject ois)))
(defn- build-via-transient
[coll]
(persistent!
12.39. TEST/SERIALIZATION.CLJ 1669
(reduce conj!
(transient coll) (map vec (partition 2 (range 1000))))))
(defn- roundtrip
[v]
(let [rt (-> v serialize deserialize)
rt-seq (-> v seq serialize deserialize)]
(and (= v rt)
(= (seq v) (seq rt))
(= (seq v) rt-seq))))
(deftest sequable-serialization
(are [val] (roundtrip val)
; lists and related
(list)
(apply list (range 10))
(cons 0 nil)
(clojure.lang.Cons. 0 nil)
; vectors
[]
(into [] (range 10))
(into [] (range 25))
(into [] (range 100))
(into [] (range 500))
(into [] (range 1000))
; maps
{}
{:a 5 :b 0}
(apply array-map (range 100))
(apply hash-map (range 100))
; sets
#{}
#{a b c}
(set (range 10))
(set (range 25))
(set (range 100))
(set (range 500))
(set (range 1000))
(sorted-set)
(sorted-set a b c)
(apply sorted-set (reverse (range 10)))
(apply sorted-set (reverse (range 25)))
(apply sorted-set (reverse (range 100)))
(apply sorted-set (reverse (range 500)))
(apply sorted-set (reverse (range 1000)))
; queues
1670 CHAPTER 12. TEST/CLOJURE
clojure.lang.PersistentQueue/EMPTY
(into clojure.lang.PersistentQueue/EMPTY (range 50))
; lazy seqs
(lazy-seq nil)
(lazy-seq (range 50))
; array-seqs
(seq (make-array Object 10))
(seq (make-array Boolean/TYPE 10))
(seq (make-array Byte/TYPE 10))
(seq (make-array Character/TYPE 10))
(seq (make-array Double/TYPE 10))
(seq (make-array Float/TYPE 10))
(seq (make-array Integer/TYPE 10))
(seq (make-array Long/TYPE 10))
; "records"
(SerializationRecord. 0 :foo (range 20))
(struct SerializationStruct 0 :foo (range 20))
; misc seqs
(seq "s11n")
(range 50)
(rseq (apply sorted-set (reverse (range 100))))))
(deftest misc-serialization
(are [v] (= v (-> v serialize deserialize))
25/3
:keyword
::namespaced-keyword
symbol))
(deftest interned-serializations
(are [v] (identical? v (-> v serialize deserialize))
clojure.lang.RT/DEFAULT_COMPARATOR
(deftest function-serialization
(let [capture 5]
(are [f] (= capture ((-> f serialize deserialize)))
12.40. TEST/SPECIAL.CLJ 1671
(constantly 5)
(fn [] 5)
#(do 5)
(constantly capture)
(fn [] capture)
#(do capture))))
(deftest check-unserializable-objects
(are [t] (thrown? java.io.NotSerializableException (serialize t))
;; transients
(transient [])
(transient {})
(transient #{})
;; reference types
(atom nil)
(ref nil)
(agent nil)
#+
;; stateful seqs
(enumeration-seq (java.util.Collections/enumeration (range 50)))
(iterator-seq (.iterator (range 50)))))
12.40 test/special.clj
test/special.clj
\getchunk{Clojure Copyright}
;;
;; Test special forms, macros and metadata
;;
(ns clojure.test-clojure.special
(:use clojure.test))
; http://clojure.org/special_forms
; let, letfn
; quote
; var
; fn
1672 CHAPTER 12. TEST/CLOJURE
12.41 test/string.clj
test/string.clj
(ns clojure.test-clojure.string
(:require [clojure.string :as s])
(:use clojure.test))
(deftest t-split
(is (= ["a" "b"] (s/split "a-b" #"-")))
(is (= ["a" "b-c"] (s/split "a-b-c" #"-" 2)))
(is (vector? (s/split "abc" #"-"))))
(deftest t-reverse
(is (= "tab" (s/reverse "bat"))))
(deftest t-replace
(is (= "faabar" (s/replace "foobar" \o \a)))
(is (= "barbarbar" (s/replace "foobarfoo" "foo" "bar")))
(is (= "FOObarFOO" (s/replace "foobarfoo" #"foo" s/upper-case))))
(deftest t-replace-first
(is (= "barbarfoo" (s/replace-first "foobarfoo" "foo" "bar")))
(is (= "barbarfoo" (s/replace-first "foobarfoo" #"foo" "bar")))
(is (= "z.ology" (s/replace-first "zoology" \o \.)))
(is (= "FOObarfoo" (s/replace-first "foobarfoo" #"foo" s/upper-case))))
(deftest t-join
(are [x coll] (= x (s/join coll))
"" nil
"" []
"1" [1]
"12" [1 2])
(are [x sep coll] (= x (s/join sep coll))
"1,2,3" \, [1 2 3]
"" \, []
"1" \, [1]
"1 and-a 2 and-a 3" " and-a " [1 2 3]))
(deftest t-trim-newline
(is (= "foo" (s/trim-newline "foo\n")))
(is (= "foo" (s/trim-newline "foo\r\n")))
(is (= "foo" (s/trim-newline "foo")))
12.41. TEST/STRING.CLJ 1673
(deftest t-capitalize
(is (= "Foobar" (s/capitalize "foobar")))
(is (= "Foobar" (s/capitalize "FOOBAR"))))
(deftest t-triml
(is (= "foo " (s/triml " foo ")))
(is (= "" (s/triml " "))))
(deftest t-trimr
(is (= " foo" (s/trimr " foo ")))
(is (= "" (s/trimr " "))))
(deftest t-trim
(is (= "foo" (s/trim " foo \r\n"))))
(deftest t-upper-case
(is (= "FOOBAR" (s/upper-case "Foobar"))))
(deftest t-lower-case
(is (= "foobar" (s/lower-case "FooBar"))))
(deftest nil-handling
(are [f args] (thrown? NullPointerException (apply f args))
s/reverse [nil]
s/replace [nil #"foo" "bar"]
s/replace-first [nil #"foo" "bar"]
s/capitalize [nil]
s/upper-case [nil]
s/lower-case [nil]
s/split [nil #"-"]
s/split [nil #"-" 1]
s/trim [nil]
s/triml [nil]
s/trimr [nil]
s/trim-newline [nil]))
(deftest char-sequence-handling
(are [result f args] (let [[^CharSequence s & more] args]
(= result (apply f (StringBuffer. s) more)))
"paz" s/reverse ["zap"]
"foo:bar" s/replace ["foo-bar" \- \:]
"ABC" s/replace ["abc" #"\w" s/upper-case]
"faa" s/replace ["foo" #"o" (StringBuffer. "a")]
"baz::quux" s/replace-first ["baz--quux" #"--" "::"]
"baz::quux" s/replace-first ["baz--quux" (StringBuffer. "--")
(StringBuffer. "::")]
"zim-zam" s/replace-first ["zim zam" #" " (StringBuffer. "-")]
"Pow" s/capitalize ["POW"]
1674 CHAPTER 12. TEST/CLOJURE
(deftest t-escape
(is (= "<foo&bar>"
(s/escape "<foo&bar>" {\& "&" \< "<" \> ">"})))
(is (= " \\\"foo\\\" "
(s/escape " \"foo\" " {\" "\\\""})))
(is (= "faabor"
(s/escape "foobar" {\a \o, \o \a}))))
(deftest t-blank
(is (s/blank? nil))
(is (s/blank? ""))
(is (s/blank? " "))
(is (s/blank? " \t \n \r "))
(is (not (s/blank? " foo "))))
(deftest t-split-lines
(let [result (s/split-lines "one\ntwo\r\nthree")]
(is (= ["one" "two" "three"] result))
(is (vector? result)))
(is (= (list "foo") (s/split-lines "foo"))))
12.42 test/test.clj
test/test.clj
\getchunk{Clojure Copyright}
;; by Stuart Sierra
;; January 16, 2009
(ns clojure.test-clojure.test
(:use clojure.test))
(deftest can-test-symbol
(let [x true]
(is x "Should pass"))
(let [x false]
(is x "Should fail")))
(deftest can-test-boolean
(is true "Should pass")
(is false "Should fail"))
(deftest can-test-nil
(is nil "Should fail"))
(deftest can-test-=
(is (= 2 (+ 1 1)) "Should pass")
(is (= 3 (+ 2 2)) "Should fail"))
(deftest can-test-instance
(is (instance? Long (+ 2 2)) "Should pass")
(is (instance? Float (+ 1 1)) "Should fail"))
(deftest can-test-thrown
(is (thrown? ArithmeticException (/ 1 0)) "Should pass")
;; No exception is thrown:
(is (thrown? Exception (+ 1 1)) "Should fail")
;; Wrong class of exception is thrown:
(is (thrown? ArithmeticException
(throw (RuntimeException.))) "Should error"))
(deftest can-test-thrown-with-msg
(is (thrown-with-msg? ArithmeticException
#"Divide by zero" (/ 1 0)) "Should pass")
;; Wrong message string:
(is (thrown-with-msg? ArithmeticException
#"Something else" (/ 1 0)) "Should fail")
;; No exception is thrown:
(is (thrown? Exception (+ 1 1)) "Should fail")
;; Wrong class of exception is thrown:
(is (thrown-with-msg? IllegalArgumentException
#"Divide by zero" (/ 1 0)) "Should error"))
(deftest can-catch-unexpected-exceptions
(is (= 1 (throw (Exception.))) "Should error"))
1676 CHAPTER 12. TEST/CLOJURE
(deftest can-test-method-call
(is (.startsWith "abc" "a") "Should pass")
(is (.startsWith "abc" "d") "Should fail"))
(deftest can-test-anonymous-fn
(is (#(.startsWith % "a") "abc") "Should pass")
(is (#(.startsWith % "d") "abc") "Should fail"))
(deftest can-test-regexps
(is (re-matches #"^ab.*$" "abbabba") "Should pass")
(is (re-matches #"^cd.*$" "abbabba") "Should fail")
(is (re-find #"ab" "abbabba") "Should pass")
(is (re-find #"cd" "abbabba") "Should fail"))
#_(deftest can-test-unbound-symbol
(is (= nil does-not-exist) "Should error"))
#_(deftest can-test-unbound-function
(is (does-not-exist) "Should error"))
;; namespace.
(defn test-ns-hook []
(binding [original-report report
report custom-report]
(test-all-vars (find-ns clojure.test-clojure.test))))
12.43 test/testfixtures.clj
test/testfixtures.clj
\getchunk{Clojure Copyright}
;
;;; test_fixtures.clj: unit tests for fixtures in test.clj
;; by Stuart Sierra
;; March 28, 2009
(ns clojure.test-clojure.test-fixtures
(:use clojure.test))
(deftest can-use-once-fixtures
1678 CHAPTER 12. TEST/CLOJURE
(is (= 3 *a*))
(is (= 5 *b*)))
(deftest can-use-each-fixtures
(is (= 7 *c*))
(is (= 11 *d*)))
(deftest use-fixtures-replaces
(is (= *n* 1)))
12.44 test/transients.clj
test/transients.clj
(ns clojure.test-clojure.transients
(:use clojure.test))
(deftest popping-off
(testing "across a node boundary"
(are [n]
(let [v (-> (range n) vec)]
(= (subvec v 0 (- n 2)) (-> v transient pop! pop! persistent!)))
33 (+ 32 (inc (* 32 32))) (+ 32 (inc (* 32 32 32)))))
(testing "off the end"
(is (thrown-with-msg? IllegalStateException #"Cant pop empty vector"
(-> [] transient pop!)))))
12.45 test/vars.clj
test/vars.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.vars
(:use clojure.test))
; http://clojure.org/vars
12.45. TEST/VARS.CLJ 1679
; def
; defn defn- defonce
(def ^:dynamic a)
(deftest test-binding
(are [x y] (= x y)
(eval (binding [a 4] a)) 4 ; regression in Clojure SVN r1370
))
(deftest test-with-local-vars
(let [factorial (fn [x]
(with-local-vars [acc 1, cnt x]
(while (> @cnt 0)
(var-set acc (* @acc @cnt))
(var-set cnt (dec @cnt)))
@acc))]
(is (= (factorial 5) 120))))
(deftest test-with-precision
(are [x y] (= x y)
(with-precision 4 (+ 3.5555555M 1)) 4.556M
(with-precision 6 (+ 3.5555555M 1)) 4.55556M
(with-precision 6 :rounding CEILING (+ 3.5555555M 1)) 4.55556M
(with-precision 6 :rounding FLOOR (+ 3.5555555M 1)) 4.55555M
(with-precision 6 :rounding HALF_UP (+ 3.5555555M 1)) 4.55556M
(with-precision 6 :rounding HALF_DOWN (+ 3.5555555M 1)) 4.55556M
(with-precision 6 :rounding HALF_EVEN (+ 3.5555555M 1)) 4.55556M
(with-precision 6 :rounding UP (+ 3.5555555M 1)) 4.55556M
(with-precision 6 :rounding DOWN (+ 3.5555555M 1)) 4.55555M
(with-precision 6 :rounding UNNECESSARY (+ 3.5555M 1)) 4.5555M))
(deftest test-settable-math-context
(is (=
(clojure.main/with-bindings
(set! *math-context* (java.math.MathContext. 8))
(+ 3.55555555555555M 1))
4.5555556M)))
; set-validator get-validator
(deftest test-with-redefs-fn
(let [p (promise)]
(with-redefs-fn {#stub-me :temp}
(fn []
(.start (Thread. #(deliver p stub-me)))
@p))
(is (= :temp @p))
(is (= :original stub-me))))
(deftest test-with-redefs
(let [p (promise)]
(with-redefs [stub-me :temp]
(.start (Thread. #(deliver p stub-me)))
@p)
(is (= :temp @p))
(is (= :original stub-me))))
(deftest test-with-redefs-throw
(let [p (promise)]
(is (thrown? Exception
(with-redefs [stub-me :temp]
(deliver p stub-me)
(throw (Exception. "simulated failure in with-redefs")))))
(is (= :temp @p))
(is (= :original stub-me))))
12.46 test/vectors.clj
test/vectors.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.vectors
(:use clojure.test))
(deftest test-reversed-vec
(let [r (range 6)
v (into (vector-of :int) r)
reversed (.rseq v)]
(testing "returns the right impl"
(is (= clojure.lang.APersistentVector$RSeq (class reversed))))
(testing "RSeq methods"
12.46. TEST/VECTORS.CLJ 1681
(is (= [5 4 3 2 1 0] reversed))
(is (= 5 (.index reversed)))
(is (= 5 (.first reversed)))
(is (= [4 3 2 1 0] (.next reversed)))
(is (= [3 2 1 0] (.. reversed next next)))
(is (= 6 (.count reversed))))
(testing "clojure calling through"
(is (= 5 (first reversed)))
(is (= 5 (nth reversed 0))))
(testing "empty reverses to nil"
(is (nil? (.. v empty rseq))))))
(deftest test-vecseq
(let [r (range 100)
vs (into (vector-of :int) r)
vs-1 (next vs)
vs-32 (.chunkedNext (seq vs))]
(testing "="
(are [a b] (= a b)
vs vs
vs-1 vs-1
vs-32 vs-32)
(are [a b] (not= a b)
vs vs-1
vs-1 vs
vs vs-32
vs-32 vs))
(testing "IPersistentCollection.empty"
(are [a]
(identical? clojure.lang.PersistentList/EMPTY (.empty (seq a)))
vs vs-1 vs-32))
(testing "IPersistentCollection.cons"
(are [result input] (= result (.cons input :foo))
[:foo 1] (seq (into (vector-of :int) [1]))))
(testing "IPersistentCollection.count"
(are [ct s] (= ct (.count (seq s)))
100 vs
99 vs-1
68 vs-32)
;; cant manufacture this scenario: ASeq defers to Counted, but
;; LazySeq doesnt, so Counted never gets checked on reified seq
;; below
#_(testing "hops to counted when available"
(is (= 200
(.count (concat
(seq vs)
(reify clojure.lang.ISeq
(seq [this] this)
clojure.lang.Counted
(count [_] 100))))))))
1682 CHAPTER 12. TEST/CLOJURE
(testing "IPersistentCollection.equiv"
(are [a b] (true? (.equiv a b))
vs vs
vs-1 vs-1
vs-32 vs-32
vs r)
(are [a b] (false? (.equiv a b))
vs vs-1
vs-1 vs
vs vs-32
vs-32 vs
vs nil))))
(deftest test-vec-compare
(let [nums (range 1 100)
; randomly replaces a single item with the given value
rand-replace
(fn[val]
(let [r (rand-int 99)]
(concat (take r nums) [val] (drop (inc r) nums))))
; all num sequences in map
num-seqs {:standard nums
:empty ()
; different lengths
:longer (concat nums [100])
:shorter (drop-last nums)
; greater by value
:first-greater (concat [100] (next nums))
:last-greater (concat (drop-last nums) [100])
:rand-greater-1 (rand-replace 100)
:rand-greater-2 (rand-replace 100)
:rand-greater-3 (rand-replace 100)
; lesser by value
:first-lesser (concat [0] (next nums))
:last-lesser (concat (drop-last nums) [0])
:rand-lesser-1 (rand-replace 0)
:rand-lesser-2 (rand-replace 0)
:rand-lesser-3 (rand-replace 0)}
; a way to create compare values based on num-seqs
create-vals
(fn [base-val]
(zipmap (keys num-seqs)
(map #(into base-val %1) (vals num-seqs))))
; Vecs made of int primitives
int-vecs (create-vals (vector-of :int))
; Vecs made of long primitives
long-vecs (create-vals (vector-of :long))
; standard boxing vectors
regular-vecs (create-vals [])
; the standard int Vec for comparisons
12.46. TEST/VECTORS.CLJ 1683
(:rand-lesser-1 regular-vecs)
(:rand-lesser-2 int-vecs)
(:rand-lesser-2 long-vecs)
(:rand-lesser-2 regular-vecs)
(:rand-lesser-3 int-vecs)
(:rand-lesser-3 long-vecs)
(:rand-lesser-3 regular-vecs)))
(testing "greater"
(are [x] (= 1 (compare int-vec x))
nil
(:empty int-vecs)
(:empty long-vecs)
(:empty regular-vecs)
(:shorter int-vecs)
(:shorter long-vecs)
(:shorter regular-vecs)
(:first-lesser int-vecs)
(:first-lesser long-vecs)
(:first-lesser regular-vecs)
(:last-lesser int-vecs)
(:last-lesser long-vecs)
(:last-lesser regular-vecs)
(:rand-lesser-1 int-vecs)
(:rand-lesser-1 long-vecs)
(:rand-lesser-1 regular-vecs)
(:rand-lesser-2 int-vecs)
(:rand-lesser-2 long-vecs)
(:rand-lesser-2 regular-vecs)
(:rand-lesser-3 int-vecs)
(:rand-lesser-3 long-vecs)
(:rand-lesser-3 regular-vecs))
(are [x] (= 1 (compare x int-vec))
(:longer int-vecs)
(:longer long-vecs)
(:longer regular-vecs)
(:first-greater int-vecs)
(:first-greater long-vecs)
(:first-greater regular-vecs)
(:last-greater int-vecs)
(:last-greater long-vecs)
(:last-greater regular-vecs)
(:rand-greater-1 int-vecs)
(:rand-greater-1 long-vecs)
(:rand-greater-1 regular-vecs)
(:rand-greater-2 int-vecs)
(:rand-greater-2 long-vecs)
(:rand-greater-2 regular-vecs)
(:rand-greater-3 int-vecs)
(:rand-greater-3 long-vecs)
(:rand-greater-3 regular-vecs))))
12.46. TEST/VECTORS.CLJ 1685
(testing "Comparable.compareTo"
(testing "incompatible"
(is (thrown? NullPointerException (.compareTo int-vec nil)))
(are [x] (thrown? ClassCastException (.compareTo int-vec x))
()
{}
#{}
(sorted-set)
(sorted-map)
nums
1))
(testing "identical"
(is (= 0 (.compareTo int-vec int-vec))))
(testing "equivalent"
(are [x] (= 0 (.compareTo int-vec x))
(:standard long-vecs)
(:standard regular-vecs)))
(testing "lesser"
(are [x] (= -1 (.compareTo int-vec x))
(:longer int-vecs)
(:longer long-vecs)
(:longer regular-vecs)
(:first-greater int-vecs)
(:first-greater long-vecs)
(:first-greater regular-vecs)
(:last-greater int-vecs)
(:last-greater long-vecs)
(:last-greater regular-vecs)
(:rand-greater-1 int-vecs)
(:rand-greater-1 long-vecs)
(:rand-greater-1 regular-vecs)
(:rand-greater-2 int-vecs)
(:rand-greater-2 long-vecs)
(:rand-greater-2 regular-vecs)
(:rand-greater-3 int-vecs)
(:rand-greater-3 long-vecs)
(:rand-greater-3 regular-vecs)))
(testing "greater"
(are [x] (= 1 (.compareTo int-vec x))
(:empty int-vecs)
(:empty long-vecs)
(:empty regular-vecs)
(:shorter int-vecs)
(:shorter long-vecs)
(:shorter regular-vecs)
(:first-lesser int-vecs)
(:first-lesser long-vecs)
(:first-lesser regular-vecs)
(:last-lesser int-vecs)
(:last-lesser long-vecs)
1686 CHAPTER 12. TEST/CLOJURE
(:last-lesser regular-vecs)
(:rand-lesser-1 int-vecs)
(:rand-lesser-1 long-vecs)
(:rand-lesser-1 regular-vecs)
(:rand-lesser-2 int-vecs)
(:rand-lesser-2 long-vecs)
(:rand-lesser-2 regular-vecs)
(:rand-lesser-3 int-vecs)
(:rand-lesser-3 long-vecs)
(:rand-lesser-3 regular-vecs))))))
(deftest test-vec-associative
(let [empty-v (vector-of :long)
v (into empty-v (range 1 6))]
(testing "Associative.containsKey"
(are [x] (.containsKey v x)
0 1 2 3 4)
(are [x] (not (.containsKey v x))
-1 -100 nil [] "" #"" #{} 5 100)
(are [x] (not (.containsKey empty-v x))
0 1))
(testing "contains?"
(are [x] (contains? v x)
0 2 4)
(are [x] (not (contains? v x))
-1 -100 nil "" 5 100)
(are [x] (not (contains? empty-v x))
0 1))
(testing "Associative.entryAt"
(are [idx val] (= (clojure.lang.MapEntry. idx val)
(.entryAt v idx))
0 1
2 3
4 5)
(are [idx] (nil? (.entryAt v idx))
-5 -1 5 10 nil "")
(are [idx] (nil? (.entryAt empty-v idx))
0 1))))
12.47 test/java5.clj
test/java5.clj
(defn annotation->map
"Converts a Java annotation (which conceals data)
into a map (which makes is usable). Not lazy.
Works recursively. Returns non-annotations unscathed."
[#^java.lang.annotation.Annotation o]
(cond
(instance? Annotation o)
(let [type (.annotationType o)
itfs (-> (into #{type} (supers type))
(disj java.lang.annotation.Annotation))
data-methods (into #{} (mapcat #(.getDeclaredMethods %) itfs))]
(into
{:annotationType (.annotationType o)}
(map
(fn [m] [(keyword (.getName m))
(annotation->map (.invoke m o nil))])
data-methods)))
(or (sequential? o) (.isArray (class o)))
(map annotation->map o)
:else o))
(def expected-annotations
#{{:annotationType java.lang.annotation.Retention,
:value RetentionPolicy/RUNTIME}
{:annotationType java.lang.Deprecated}})
(deftest test-annotations-on-type
(is (=
expected-annotations
(into #{} (map annotation->map (.getAnnotations Bar))))))
(deftest test-annotations-on-field
(is (=
expected-annotations
(into #{}
1688 CHAPTER 12. TEST/CLOJURE
(deftest test-annotations-on-method
(is (=
expected-annotations
(into #{}
(map annotation->map
(.getAnnotations (.getMethod Bar "foo" nil)))))))
12.48 test/java6andlater.clj
test/java6andlater.clj
(defn annotation->map
"Converts a Java annotation (which conceals data)
into a map (which makes is usable). Not lazy.
Works recursively. Returns non-annotations unscathed."
[#^java.lang.annotation.Annotation o]
(cond
(instance? Annotation o)
(let [type (.annotationType o)
itfs (-> (into #{type} (supers type))
(disj java.lang.annotation.Annotation))
data-methods (into #{} (mapcat #(.getDeclaredMethods %) itfs))]
(into
{:annotationType (.annotationType o)}
(map
(fn [m] [(keyword (.getName m))
(annotation->map (.invoke m o nil))])
data-methods)))
(or (sequential? o) (.isArray (class o)))
(map annotation->map o)
:else o))
(def expected-annotations
#{{:annotationType java.lang.annotation.Retention,
:value RetentionPolicy/RUNTIME}
{:annotationType javax.xml.ws.WebServiceRefs,
:value [{:annotationType javax.xml.ws.WebServiceRef,
:name "fred", :mappedName "", :type java.lang.String,
:wsdlLocation "", :value java.lang.Object}
{:annotationType javax.xml.ws.WebServiceRef,
:name "ethel", :mappedName "lucy",
:type java.lang.Object,
:wsdlLocation "", :value java.lang.Object}]}
{:annotationType javax.xml.ws.soap.Addressing,
:enabled false, :required true}
{:annotationType javax.annotation.processing.SupportedOptions,
:value ["foo" "bar" "baz"]}
{:annotationType java.lang.Deprecated}})
(deftest test-annotations-on-type
(is (=
expected-annotations
(into #{} (map annotation->map (.getAnnotations Bar))))))
(deftest test-annotations-on-field
1690 CHAPTER 12. TEST/CLOJURE
(is (=
expected-annotations
(into #{}
(map annotation->map (.getAnnotations (.getField Bar "b")))))))
(deftest test-annotations-on-method
(is (=
expected-annotations
(into #{}
(map annotation->map
(.getAnnotations (.getMethod Bar "foo" nil)))))))
12.49 test/examples.clj
test/examples.clj
\getchunk{Clojure Copyright}
(ns ^{:doc "Test classes that are AOT-compile for the tests in
clojure.test-clojure.genclass."
:author "Stuart Halloway, Daniel Solano Gmez"}
clojure.test-clojure.genclass.examples)
(definterface ExampleInterface
(foo [a])
(foo [a b])
(foo [a #^int b]))
(defn -foo-Object-Object
[_ o1 o2]
"foo with o, o")
(defn -foo-Object-int
[_ o i]
"foo with o, i")
(gen-class :name
^{Deprecated {}
12.50. TEST/IO.CLJ 1691
12.50 test/io.clj
test/io.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.java.io
(:use clojure.test clojure.java.io
[clojure.test-helper :only [platform-newlines]])
(:import (java.io File BufferedInputStream
FileInputStream InputStreamReader InputStream
FileOutputStream OutputStreamWriter OutputStream
ByteArrayInputStream ByteArrayOutputStream)
(java.net URL URI Socket ServerSocket)))
(defn temp-file
[prefix suffix]
(doto (File/createTempFile prefix suffix)
(.deleteOnExit)))
(deftest test-spit-and-slurp
(let [f (temp-file "clojure.java.io" "test")]
(spit f "foobar")
(is (= "foobar" (slurp f)))
(spit f "foobar" :encoding "UTF-16")
(is (= "foobar" (slurp f :encoding "UTF-16")))
(testing "deprecated arity"
(is (=
(platform-newlines
"WARNING: (slurp f enc) is deprecated, use (slurp f :encoding enc).\n")
(with-out-str
(is (= "foobar" (slurp f "UTF-16")))))))))
1692 CHAPTER 12. TEST/CLOJURE
(deftest test-streams-defaults
(let [f (temp-file "clojure.java.io" "test-reader-writer")
content "testing"]
(try
(is (thrown? Exception (reader (Object.))))
(is (thrown? Exception (writer (Object.))))
(defn data-fixture
"in memory fixture data for tests"
[encoding]
(let [bs (.getBytes "hello" encoding)
cs (.toCharArray "hello")
i (ByteArrayInputStream. bs)
r (InputStreamReader. i)
o (ByteArrayOutputStream.)
w (OutputStreamWriter. o)]
{:bs bs
:i i
:r r
:o o
:s "hello"
:cs cs
:w w}))
12.50. TEST/IO.CLJ 1693
(deftest test-copy
(dorun
(for [{:keys [in out flush] :as test}
[{:in :i :out :o}
{:in :i :out :w}
{:in :r :out :o}
{:in :r :out :w}
{:in :cs :out :o}
{:in :cs :out :w}
{:in :bs :out :o}
{:in :bs :out :w}]
opts
[{} {:buffer-size 256}]]
(let [{:keys [s o] :as d} (data-fixture "UTF-8")]
(apply copy (in d) (out d) (flatten (vec opts)))
#_(when (= out :w) (.flush (:w d)))
(.flush (out d))
(bytes-should-equal (.getBytes s "UTF-8")
(.toByteArray o)
(str "combination " test opts))))))
(deftest test-copy-encodings
(testing "from inputstream UTF-16 to writer UTF-8"
(let [{:keys [i s o w bs]} (data-fixture "UTF-16")]
(copy i w :encoding "UTF-16")
(.flush w)
(bytes-should-equal (.getBytes s "UTF-8") (.toByteArray o) "")))
(testing "from reader UTF-8 to output-stream UTF-16"
(let [{:keys [r o s]} (data-fixture "UTF-8")]
(copy r o :encoding "UTF-16")
(bytes-should-equal (.getBytes s "UTF-16") (.toByteArray o) ""))))
(deftest test-as-file
(are [result input] (= result (as-file input))
(File. "foo") "foo"
(File. "bar") (File. "bar")
(File. "baz") (URL. "file:baz")
(File. "quux") (URI. "file:quux")
nil nil))
(deftest test-file
(are [result args] (= (File. result) (apply file args))
"foo" ["foo"]
"foo/bar" ["foo" "bar"]
"foo/bar/baz" ["foo" "bar" "baz"]))
(deftest test-as-url
(are [file-part input]
(= (URL. (str "file:" file-part)) (as-url input))
"foo" "file:foo"
1694 CHAPTER 12. TEST/CLOJURE
(deftest test-delete-file
(let [file (temp-file "test" "deletion")
not-file (File. (str (java.util.UUID/randomUUID)))]
(delete-file (.getAbsolutePath file))
(is (not (.exists file)))
(is (thrown? java.io.IOException (delete-file not-file)))
(is (= :silently (delete-file not-file :silently)))))
(deftest test-as-relative-path
(testing "strings"
(is (= "foo" (as-relative-path "foo"))))
(testing "absolute path strings are forbidden"
(is (thrown? IllegalArgumentException
(as-relative-path (.getAbsolutePath (File. "baz"))))))
(testing "relative File paths"
(is (= "bar" (as-relative-path (File. "bar")))))
(testing "absolute File paths are forbidden"
(is (thrown? IllegalArgumentException
(as-relative-path
(File. (.getAbsolutePath (File. "quux"))))))))
(deftest test-input-stream
(let [file (temp-file "test-input-stream" "txt")
bytes (.getBytes "foobar")]
(spit file "foobar")
(doseq [[expr msg]
[[file File]
[(FileInputStream. file) FileInputStream]
[(BufferedInputStream. (FileInputStream. file))
BufferedInputStream]
[(.. file toURI) URI]
[(.. file toURI toURL) URL]
[(.. file toURI toURL toString) "URL as String"]
[(.. file toString) "File as String"]]]
(with-open [s (input-stream expr)]
(stream-should-have s bytes msg)))))
(deftest test-streams-buffering
(let [data (.getBytes "")]
12.50. TEST/IO.CLJ 1695
(deftest test-resource
(is (nil? (resource "non/existent/resource")))
(is (instance? URL (resource "clojure/core.clj")))
(let [file (temp-file "test-resource" "txt")
url (as-url (.getParentFile file))
loader (java.net.URLClassLoader. (into-array [url]))]
(is (nil? (resource "non/existent/resource" loader)))
(is (instance? URL (resource (.getName file) loader)))))
(deftest test-make-parents
(let [tmp (System/getProperty "java.io.tmpdir")]
(delete-file
(file tmp "test-make-parents" "child" "grandchild") :silently)
(delete-file
(file tmp "test-make-parents" "child") :silently)
(delete-file
(file tmp "test-make-parents") :silently)
(make-parents tmp "test-make-parents" "child" "grandchild")
(is (.isDirectory (file tmp "test-make-parents" "child")))
(is (not
(.isDirectory
(file tmp "test-make-parents" "child" "grandchild"))))
(delete-file (file tmp "test-make-parents" "child"))
(delete-file (file tmp "test-make-parents"))))
(deftest test-socket-iofactory
(let [port 65321
server-socket (ServerSocket. port)
client-socket (Socket. "localhost" port)]
(try
(is (instance? InputStream (input-stream client-socket)))
(is (instance? OutputStream (output-stream client-socket)))
(finally (.close server-socket)
(.close client-socket)))))
-
1696 CHAPTER 12. TEST/CLOJURE
12.51 test/javadoc.clj
test/javadoc.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.java.javadoc
(:use clojure.test
[clojure.java.javadoc :as j])
(:import (java.io File)))
(deftest javadoc-url-test
(testing "for a core api"
(binding [*feeling-lucky* false]
(are [x y] (= x (#j/javadoc-url y))
nil "foo.Bar"
(str *core-java-api* "java/lang/String.html")
"java.lang.String")))
(testing "for a remote javadoc"
(binding [*remote-javadocs*
(ref (sorted-map "java." "http://example.com/"))]
(is (= "http://example.com/java/lang/Number.html"
(#j/javadoc-url "java.lang.Number"))))))
12.52 test/shell.clj
test/shell.clj
\getchunk{Clojure Copyright}
(ns clojure.test-clojure.java.shell
(:use clojure.test
[clojure.java.shell :as sh])
(:import (java.io File)))
(deftest test-parse-args
(are [x y] (= x y)
[[] {:in-enc default-enc
:out-enc default-enc
:dir nil
12.53. TEST/TESTCLFORMAT.CLJ 1697
(deftest test-with-sh-dir
(are [x y] (= x y)
nil *sh-dir*
"foo" (with-sh-dir "foo" *sh-dir*)))
(deftest test-with-sh-env
(are [x y] (= x y)
nil *sh-env*
{:KEY "VAL"} (with-sh-env {:KEY "VAL"} *sh-env*)))
(deftest test-as-env-strings
(are [x y] (= x y)
nil (#sh/as-env-strings nil)
["FOO=BAR"]
(seq (#sh/as-env-strings {"FOO" "BAR"}))
["FOO_SYMBOL=BAR"]
(seq (#sh/as-env-strings {FOO_SYMBOL "BAR"}))
["FOO_KEYWORD=BAR"]
(seq (#sh/as-env-strings {:FOO_KEYWORD "BAR"}))))
12.53 test/testclformat.clj
test/testclformat.clj
1698 CHAPTER 12. TEST/CLOJURE
\getchunk{Clojure Copyright}
(in-ns clojure.test-clojure.pprint)
(simple-tests d-tests
(cl-format nil "~D" 0) "0"
(cl-format nil "~D" 2e6) "2000000"
(cl-format nil "~D" 2000000) "2000000"
(cl-format nil "~:D" 2000000) "2,000,000"
(cl-format nil "~D" 1/2) "1/2"
(cl-format nil "~D" fred) "fred"
)
(simple-tests base-tests
(cl-format nil "~{~2r~^ ~}~%" (range 10))
"0 1 10 11 100 101 110 111 1000 1001\n"
(with-out-str
(dotimes [i 35]
(binding [*print-base* (+ i 2)] ;print the decimal number 40
(write 40) ;in each base from 2 to 36
(if (zero? (mod i 10)) (prn) (cl-format true " ")))))
"101000
1111 220 130 104 55 50 44 40 37 34
31 2c 2a 28 26 24 22 20 1j 1i
1h 1g 1f 1e 1d 1c 1b 1a 19 18
17 16 15 14 "
(with-out-str
(doseq [pb [2 3 8 10 16]]
(binding [*print-radix* true ;print the integer 10 and
*print-base* pb] ;the ratio 1/10 in bases 2,
(cl-format true "~&~S ~S~%" 10 1/10)))) ;3, 8, 10, 16
"#b1010 #b1/1010\n#3r101 #3r1/101\n#o12
#o1/12\n10. #10r1/10\n#xa #x1/a\n")
12.53. TEST/TESTCLFORMAT.CLJ 1699
(simple-tests cardinal-tests
(cl-format nil "~R" 0) "zero"
(cl-format nil "~R" 4) "four"
(cl-format nil "~R" 15) "fifteen"
(cl-format nil "~R" -15) "minus fifteen"
(cl-format nil "~R" 25) "twenty-five"
(cl-format nil "~R" 20) "twenty"
(cl-format nil "~R" 200) "two hundred"
(cl-format nil "~R" 203) "two hundred three"
(simple-tests ordinal-tests
(cl-format nil "~:R" 0) "zeroth"
(cl-format nil "~:R" 4) "fourth"
(cl-format nil "~:R" 15) "fifteenth"
(cl-format nil "~:R" -15) "minus fifteenth"
(cl-format nil "~:R" 25) "twenty-fifth"
(cl-format nil "~:R" 20) "twentieth"
(cl-format nil "~:R" 200) "two hundredth"
(cl-format nil "~:R" 203) "two hundred third"
"989,842,094,490,320,942,058,747,587,584,758,375,847,593,471st = "
"448,790,329,480,948,209,384,389,429,384,029,384,029,842,098,420,"
"989,842,094,490,320,942,058,747,587,584,758,375,847,593,471"
(cl-format nil "~:R = ~:*~:D" 2e6)
"two millionth = 2,000,000")
(simple-tests ordinal1-tests
(cl-format nil "~:R" 1) "first"
(cl-format nil "~:R" 11) "eleventh"
(cl-format nil "~:R" 21) "twenty-first"
(cl-format nil "~:R" 20) "twentieth"
(cl-format nil "~:R" 220) "two hundred twentieth"
(cl-format nil "~:R" 200) "two hundredth"
(cl-format nil "~:R" 999) "nine hundred ninety-ninth"
)
(simple-tests roman-tests
(cl-format nil "~@R" 3) "III"
(cl-format nil "~@R" 4) "IV"
(cl-format nil "~@R" 9) "IX"
(cl-format nil "~@R" 29) "XXIX"
(cl-format nil "~@R" 429) "CDXXIX"
(cl-format nil "~@:R" 429) "CCCCXXVIIII"
(cl-format nil "~@:R" 3429) "MMMCCCCXXVIIII"
(cl-format nil "~@R" 3429) "MMMCDXXIX"
(cl-format nil "~@R" 3479) "MMMCDLXXIX"
(cl-format nil "~@R" 3409) "MMMCDIX"
(cl-format nil "~@R" 300) "CCC"
(cl-format nil "~@R ~D" 300 20) "CCC 20"
(cl-format nil "~@R" 5000) "5,000"
(cl-format nil "~@R ~D" 5000 20) "5,000 20"
(cl-format nil "~@R" "the quick") "the quick")
(simple-tests c-tests
(cl-format nil "~{~c~^, ~}~%" "hello") "h, e, l, l, o\n"
(cl-format nil "~{~:c~^, ~}~%" "hello") "h, e, l, l, o\n"
(cl-format nil "~@C~%" \m) "\\m\n"
(cl-format nil "~@C~%" (char 222)) "\\\n"
(cl-format nil "~@C~%" (char 8)) "\\backspace\n")
;tpd the control-C wont pass latex.
;tpd I need some latex magic to fix it.
; (cl-format nil "~@C~%" (char 3)) "\\ctrl-\n")
(simple-tests e-tests
(cl-format nil "*~E*" 0.0) "*0.0E+0*"
(cl-format nil "*~6E*" 0.0) "*0.0E+0*"
(cl-format nil "*~6,0E*" 0.0) "* 0.E+0*"
(cl-format nil "*~7,2E*" 0.0) "*0.00E+0*"
(cl-format nil "*~5E*" 0.0) "*0.E+0*"
(cl-format nil "*~10,2,2,,?E*" 2.8E120) "*??????????*"
1702 CHAPTER 12. TEST/CLOJURE
(simple-tests $-tests
(cl-format nil "~$" 22.3) "22.30"
(cl-format nil "~$" 22.375) "22.38"
(cl-format nil "~3,5$" 22.375) "00022.375"
(cl-format nil "~3,5,8$" 22.375) "00022.375"
(cl-format nil "~3,5,10$" 22.375) " 00022.375"
(cl-format nil "~3,5,14@$" 22.375) " +00022.375"
(cl-format nil "~3,5,14@$" 22.375) " +00022.375"
(cl-format nil "~3,5,14@:$" 22.375) "+ 00022.375"
(cl-format nil "~3,,14@:$" 0.375) "+ 0.375"
(cl-format nil "~1,1$" -12.0) "-12.0"
(cl-format nil "~1,1$" 12.0) "12.0"
(cl-format nil "~1,1$" 12.0) "12.0"
(cl-format nil "~1,1@$" 12.0) "+12.0"
(cl-format nil "~1,1,8, @:$" 12.0) "+ 12.0"
(cl-format nil "~1,1,8, @$" 12.0) " +12.0"
(cl-format nil "~1,1,8, :$" 12.0) " 12.0"
(cl-format nil "~1,1,8, $" 12.0) " 12.0"
(cl-format nil "~1,1,8, @:$" -12.0) "- 12.0"
(cl-format nil "~1,1,8, @$" -12.0) " -12.0"
(cl-format nil "~1,1,8, :$" -12.0) "- 12.0"
(cl-format nil "~1,1,8, $" -12.0) " -12.0"
(cl-format nil "~1,1$" 0.001) "0.0"
(cl-format nil "~2,1$" 0.001) "0.00"
(cl-format nil "~1,1,6$" 0.001) " 0.0"
(cl-format nil "~1,1,6$" 0.0015) " 0.0"
(cl-format nil "~2,1,6$" 0.005) " 0.01"
(cl-format nil "~2,1,6$" 0.01) " 0.01"
(cl-format nil "~$" 0.099) "0.10"
(cl-format nil "~1$" 0.099) "0.1"
(cl-format nil "~1$" 0.1) "0.1"
(cl-format nil "~1$" 0.99) "1.0"
(cl-format nil "~1$" -0.99) "-1.0")
(simple-tests f-tests
(cl-format nil "~,1f" -12.0) "-12.0"
(cl-format nil "~,0f" 9.4) "9."
(cl-format nil "~,0f" 9.5) "10."
(cl-format nil "~,0f" -0.99) "-1."
(cl-format nil "~,1f" -0.99) "-1.0"
(cl-format nil "~,2f" -0.99) "-0.99"
(cl-format nil "~,3f" -0.99) "-0.990"
(cl-format nil "~,0f" 0.99) "1."
(cl-format nil "~,1f" 0.99) "1.0"
12.53. TEST/TESTCLFORMAT.CLJ 1703
(simple-tests ampersand-tests
(cl-format nil
"The quick brown ~a jumped over ~d lazy dogs" elephant 5)
"The quick brown elephant jumped over 5 lazy dogs"
(cl-format nil
"The quick brown ~&~a jumped over ~d lazy dogs" elephant 5)
"The quick brown \nelephant jumped over 5 lazy dogs"
(cl-format nil (platform-newlines
"The quick brown ~&~a jumped\n~& over ~d lazy dogs") elephant 5)
"The quick brown \nelephant jumped\n over 5 lazy dogs"
(cl-format nil (platform-newlines
"~&The quick brown ~&~a jumped\n~& over ~d lazy dogs") elephant 5)
"The quick brown \nelephant jumped\n over 5 lazy dogs"
(cl-format nil (platform-newlines
"~3&The quick brown ~&~a jumped\n~& over ~d lazy dogs") elephant 5)
"\n\nThe quick brown \nelephant jumped\n over 5 lazy dogs"
(cl-format nil
"~@{~&The quick brown ~a jumped over ~d lazy dogs~}"
elephant 5 fox 10)
(str "The quick brown elephant jumped over 5 lazy dogs\n"
"The quick brown fox jumped over 10 lazy dogs")
(cl-format nil "I ~[dont ~:;d~&o ~]have one~%" 0) "I dont have one\n"
(cl-format nil "I ~[dont ~:;d~&o ~]have one~%" 1) "I d\no have one\n")
(simple-tests t-tests
(cl-format nil "~@{~&~A~8,4T~:*~A~}"
a aa aaa aaaa aaaaa aaaaaa aaaaaaa
aaaaaaaa aaaaaaaaa aaaaaaaaaa)
(str "a a\naa aa\naaa aaa\naaaa aaaa\naaaaa "
"aaaaa\naaaaaa aaaaaa\naaaaaaa aaaaaaa\naaaaaaaa "
"aaaaaaaa\naaaaaaaaa aaaaaaaaa\naaaaaaaaaa aaaaaaaaaa")
(cl-format nil "~@{~&~A~,4T~:*~A~}"
a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa
aaaaaaaaaa)
(str "a a\naa aa\naaa aaa\naaaa aaaa\naaaaa aaaaa\naaaaaa "
" aaaaaa\naaaaaaa aaaaaaa\naaaaaaaa aaaaaaaa\naaaaaaaaa "
"aaaaaaaaa\naaaaaaaaaa aaaaaaaaaa")
(cl-format nil "~@{~&~A~2,6@T~:*~A~}" a aa aaa aaaa aaaaa
aaaaaa aaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaaaa)
(str "a a\naa aa\naaa aaa\naaaa aaaa\naaaaa "
"aaaaa\naaaaaa aaaaaa\naaaaaaa aaaaaaa\naaaaaaaa"
1704 CHAPTER 12. TEST/CLOJURE
(simple-tests paren-tests
(cl-format nil "~(PLEASE SPEAK QUIETLY IN HERE~)")
"please speak quietly in here"
(cl-format nil "~@(PLEASE SPEAK QUIETLY IN HERE~)")
"Please speak quietly in here"
(cl-format nil "~@:(but this Is imporTant~)")
"BUT THIS IS IMPORTANT"
(cl-format nil "~:(the greAt gatsby~)!")
"The Great Gatsby!"
;; Test cases from CLtL 18.3 - string-upcase, et al.
(cl-format nil "~@:(~A~)" "Dr. Livingstone, I presume?")
"DR. LIVINGSTONE, I PRESUME?"
(cl-format nil "~(~A~)" "Dr. Livingstone, I presume?")
"dr. livingstone, i presume?"
(cl-format nil "~:(~A~)" " hello ")
" Hello "
(cl-format nil "~:(~A~)"
"occlUDeD cASEmenTs FOreSTAll iNADVertent DEFenestraTION")
"Occluded Casements Forestall Inadvertent Defenestration"
(cl-format nil "~:(~A~)" kludgy-hash-search) "Kludgy-Hash-Search"
(cl-format nil "~:(~A~)" "DONT!") "DonT!" ;not "Dont!"
(cl-format nil "~:(~A~)" "pipe 13a, foo16c") "Pipe 13a, Foo16c"
)
(simple-tests square-bracket-tests
;; Tests for format without modifiers
(cl-format nil "I ~[dont ~]have one~%" 0) "I dont have one\n"
(cl-format nil "I ~[dont ~]have one~%" 1) "I have one\n"
(cl-format nil "I ~[dont ~;do ~]have one~%" 0) "I dont have one\n"
(cl-format nil "I ~[dont ~;do ~]have one~%" 1) "I do have one\n"
(cl-format nil "I ~[dont ~;do ~]have one~%" 2) "I have one\n"
(cl-format nil "I ~[dont ~:;do ~]have one~%" 0) "I dont have one\n"
(cl-format nil "I ~[dont ~:;do ~]have one~%" 1) "I do have one\n"
(cl-format nil "I ~[dont ~:;do ~]have one~%" 2) "I do have one\n"
(cl-format nil "I ~[dont ~:;do ~]have one~%" 700) "I do have one\n"
(simple-tests curly-brace-plain-tests
;; Iteration from sublist
(cl-format nil "Coordinates are~{ [~D,~D]~}~%"
[ 0, 1, 1, 0, 3, 5, 2, 1 ])
"Coordinates are [0,1] [1,0] [3,5] [2,1]\n"
1706 CHAPTER 12. TEST/CLOJURE
(cl-format nil
"Coordinates are~{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [2 3 1])
"Coordinates are [2,3] <1>\n"
(cl-format nil
"Coordinates are~{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [ ])
"Coordinates are none\n"
)
(simple-tests curly-brace-colon-tests
;; Iteration from list of sublists
(cl-format nil "Coordinates are~:{ [~D,~D]~}~%"
[ [0, 1], [1, 0], [3, 5], [2, 1] ])
"Coordinates are [0,1] [1,0] [3,5] [2,1]\n"
(simple-tests curly-brace-at-tests
;; Iteration from main list
(cl-format nil "Coordinates are~@{ [~D,~D]~}~%"
0, 1, 1, 0, 3, 5, 2, 1)
"Coordinates are [0,1] [1,0] [3,5] [2,1]\n"
(simple-tests curly-brace-colon-at-tests
;; Iteration from sublists on the main arg list
1708 CHAPTER 12. TEST/CLOJURE
(cl-format nil
"Coordinates are~@:{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [2 3] [1])
"Coordinates are [2,3] <1>\n"
(cl-format nil
"Coordinates are~@:{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]")
"Coordinates are none\n"
)
(simple-tests angle-bracket-tests
12.53. TEST/TESTCLFORMAT.CLJ 1709
(simple-tests angle-bracket-max-column-tests
(cl-format nil
"~%;; ~{~<~%;; ~1,50:; ~A~>~}.~%"
(into []
(.split
(str "This function computes the circular thermodynamic "
"coefficient of the thrombulator angle for use in "
"determining the reaction distance" "\\s"))))
(str "\n;; This function computes the circular\n;; "
"thermodynamic coefficient of the thrombulator\n;; "
"angle for use in determining the reaction\n;; distance.\n")
(cl-format true "~%;; ~{~<~%;; ~:; ~A~>~}.~%"
(into [] (.split
(str "This function computes the circular thermodynamic coefficient"
" of the thrombulator angle for use in determining the reaction"
" distance." "\\s")))))
(simple-tests column-writer-test
(list-to-table (map #(vector % (* % %) (* % % %)) (range 1 21)) 8)
(str " 1 1 1 \n 2 4 8 \n 3 "
" 9 27 \n 4 16 64 \n 5 25 125"
" \n 6 36 216 \n 7 49 343 \n 8"
" 64 512 \n 9 81 729 \n 10 100 "
" 1000 \n 11 121 1331 \n 12 144 1728 \n"
" 13 169 2197 \n 14 196 2744 \n 15 225"
" 3375 \n 16 256 4096 \n 17 289 4913 "
" \n 18 324 5832 \n 19 361 6859 \n 20 "
"400 8000 \n"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The following tests are the various examples from the format
;; documentation in Common Lisp, the Language, 2nd edition, Chapter 22.3
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(let [x 5, y "elephant", n 3]
(simple-tests cltl-intro-tests
(format nil "foo") "foo"
(format nil "The answer is ~D." x) "The answer is 5."
(format nil "The answer is ~3D." x) "The answer is 5."
(format nil "The answer is ~3,0D." x) "The answer is 005."
(format nil "The answer is ~:D." (expt 47 x))
"The answer is 229,345,007."
(format nil "Look at the ~A!" y) "Look at the elephant!"
(format nil "Type ~:C to ~A." (char 4) "delete all your files")
"Type Control-D to delete all your files."
(format nil "~D item~:P found." n) "3 items found."
(format nil "~R dog~:[s are~; is~] here." n (= n 1))
"three dogs are here."
(format nil "~R dog~:*~[s are~; is~:;s are~] here." n)
"three dogs are here."
(format nil "Here ~[are~;is~:;are~] ~:*~R pupp~:@P." n)
"Here are three puppies."))
(simple-tests cltl-B-tests
;; CLtL didnt have the colons here, but the spec requires them
(format nil "~,, ,4:B" 0xFACE) "1111 1010 1100 1110"
(format nil "~,, ,4:B" 0x1CE) "1 1100 1110"
(format nil "~19,, ,4:B" 0xFACE) "1111 1010 1100 1110"
;; This one was a nice idea, but nothing in the spec supports it
12.53. TEST/TESTCLFORMAT.CLJ 1711
(simple-tests cltl-P-tests
(format nil "~D tr~:@P/~D win~:P" 7 1) "7 tries/1 win"
(format nil "~D tr~:@P/~D win~:P" 1 0) "1 try/0 wins"
(format nil "~D tr~:@P/~D win~:P" 1 3) "1 try/3 wins")
(simple-tests cltl-F-tests
(foo 3.14159) " 3.14| 31.42| 3.14|3.1416|3.14|3.14159"
(foo -3.14159) " -3.14|-31.42| -3.14|-3.142|-3.14|-3.14159"
(foo 100.0) "100.00|******|100.00| 100.0|100.00|100.0"
(foo 1234.0) "1234.00|******|??????|1234.0|1234.00|1234.0"
(foo 0.006) " 0.01| 0.06| 0.01| 0.006|0.01|0.006")
(simple-tests cltl-E-scale-tests
(map
(fn [k] (format nil "Scale factor ~2D~:*: |~13,6,2,VE|"
(- k 5) 3.14159)) ;Prints 13 lines
(range 13))
("Scale factor -5: | 0.000003E+06|"
"Scale factor -4: | 0.000031E+05|"
"Scale factor -3: | 0.000314E+04|"
"Scale factor -2: | 0.003142E+03|"
"Scale factor -1: | 0.031416E+02|"
1712 CHAPTER 12. TEST/CLOJURE
(simple-tests cltl-Newline-tests
(type-clash-error aref nil 2 integer vector)
"Function aref requires its second argument to be of type integer,
but it was called with an argument of type vector.\n"
(type-clash-error car 1 1 list short-float)
"Function car requires its argument to be of type list,
but it was called with an argument of type short-float.\n")
(simple-tests cltl-?-tests
(format nil "~? ~D" "<~A ~D>" ("Foo" 5) 7) "<Foo 5> 7"
(format nil "~? ~D" "<~A ~D>" ("Foo" 5 14) 7) "<Foo 5> 7"
(format nil "~@? ~D" "<~A ~D>" "Foo" 5 7) "<Foo 5> 7"
(format nil "~@? ~D" "<~A ~D>" "Foo" 5 14 7) "<Foo 5> 14")
12.53. TEST/TESTCLFORMAT.CLJ 1713
(simple-tests cltl-paren-tests
(format nil "~@R ~(~@R~)" 14 14) "XIV xiv"
(f 0) "Zero errors detected."
(f 1) "One error detected."
(f 23) "Twenty-three errors detected.")
(simple-tests cltl-curly-bracket-tests
(format nil
"The winners are:~{ ~S~}."
(fred harry jill))
"The winners are: fred harry jill."
(simple-tests cltl-angle-bracket-tests
(format nil "~10<foo~;bar~>") "foo bar"
(format nil "~10:<foo~;bar~>") " foo bar"
(format nil "~10:@<foo~;bar~>") " foo bar "
(format nil "~10<foobar~>") " foobar"
(format nil "~10:<foobar~>") " foobar"
1714 CHAPTER 12. TEST/CLOJURE
(simple-tests cltl-up-tests
(format nil donestr) "Done."
(format nil donestr 3) "Done. 3 warnings."
(format nil donestr 1 5) "Done. 1 warning. 5 errors."
(format nil tellstr 23) "Twenty-three."
(format nil tellstr nil "losers") "Losers."
(format nil tellstr 23 "losers") "Twenty-three losers."
(format nil "~15<~S~;~^~S~;~^~S~>" foo)
" foo"
(format nil "~15<~S~;~^~S~;~^~S~>" foo bar)
"foo bar"
(format nil "~15<~S~;~^~S~;~^~S~>" foo bar baz)
"foo bar baz"))
(simple-tests cltl-up-x3j13-tests
(format nil
"~:{/~S~^ ...~}"
((hot dog) (hamburger) (ice cream) (french fries)))
"/hot .../hamburger/ice .../french ..."
(format nil
"~:{/~S~:^ ...~}"
((hot dog) (hamburger) (ice cream) (french fries)))
"/hot .../hamburger .../ice .../french"
(format nil
"~:{/~S~#:^ ...~}" ;; This is wrong in CLtL
((hot dog) (hamburger) (ice cream) (french fries)))
"/hot .../hamburger")
12.54 test1/testhelper.clj
test1/testhelper.clj
\getchunk{Clojure Copyright}
;;; test_helper.clj -- part of the pretty printer for Clojure
;; April 3, 2009
(ns clojure.test-clojure.pprint.test-helper
(:use [clojure.test :only (deftest is)]
[clojure.test-helper :only [platform-newlines]]))
12.55 test/testpretty.clj
test/testpretty.clj
\getchunk{Clojure Copyright}
;;; test_pretty.clj -- part of the pretty printer for Clojure
(in-ns clojure.test-clojure.pprint)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Unit tests for the pretty printer
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(simple-tests xp-fill-test
(binding [*print-pprint-dispatch* simple-dispatch
*print-right-margin* 38
1716 CHAPTER 12. TEST/CLOJURE
*print-miser-width* nil]
(cl-format nil "(let ~:<~@{~:<~w ~_~w~:>~^ ~:_~}~:>~_ ...)~%"
((x 4) (*print-length* nil) (z 2) (list nil))))
"(let ((x 4) (*print-length* nil)\n (z 2) (list nil))\n ...)\n"
(simple-tests xp-miser-test
(binding [*print-pprint-dispatch* simple-dispatch
*print-right-margin* 10, *print-miser-width* 9]
(cl-format nil "~:<LIST ~@_~W ~@_~W ~@_~W~:>" (first second third)))
"(LIST\n first\n second\n third)"
(simple-tests mandatory-fill-test
(cl-format nil
"<pre>~%~<Usage: ~:I~@{*~a*~^~:@_~}~:>~%</pre>~%"
[ "hello" "gooodbye" ])
"<pre>
Usage: *hello*
*gooodbye*
</pre>
")
(simple-tests prefix-suffix-test
(binding [*print-pprint-dispatch* simple-dispatch
*print-right-margin* 10, *print-miser-width* 10]
(cl-format nil "~<{~;LIST ~@_~W ~@_~W ~@_~W~;}~:>"
(first second third)))
"{LIST\n first\n second\n third}")
(simple-tests pprint-test
(binding [*print-pprint-dispatch* simple-dispatch]
(write
(defn foo [x y]
(let [result (* x y)]
(if (> result 400)
(cl-format true "That number is too big")
12.55. TEST/TESTPRETTY.CLJ 1717
(with-pprint-dispatch code-dispatch
(write
(defn foo [x y]
(let [result (* x y)]
(if (> result 400)
(cl-format true "That number is too big")
(cl-format true "The result of ~d x ~d is ~d" x y result))))
:stream nil))
"(defn foo [x y]
(let [result (* x y)]
(if (> result 400)
(cl-format true \"That number is too big\")
(cl-format true \"The result of ~d x ~d is ~d\" x y result))))"
(with-pprint-dispatch code-dispatch
(binding [*print-right-margin* 52]
(write
(add-to-buffer this (make-buffer-blob (str (char c)) nil))
:stream nil)))
"(add-to-buffer\n this\n (make-buffer-blob (str (char c)) nil))"
)
(simple-tests pprint-reader-macro-test
(with-pprint-dispatch code-dispatch
(write (read-string "(map #(first %) [[1 2 3] [4 5 6] [7]])")
:stream nil))
"(map #(first %) [[1 2 3] [4 5 6] [7]])"
(with-pprint-dispatch code-dispatch
(write (read-string "@@(ref (ref 1))")
:stream nil))
1718 CHAPTER 12. TEST/CLOJURE
(with-pprint-dispatch code-dispatch
(write (read-string "foo")
:stream nil))
"foo"
)
(simple-tests code-block-tests
(with-out-str
(with-pprint-dispatch code-dispatch
(pprint
(defn cl-format
"An implementation of a Common Lisp compatible format function"
[stream format-in & args]
(let [compiled-format
(if (string? format-in)
(compile-format format-in)
format-in)
navigator (init-navigator args)]
(execute-format stream compiled-format navigator))))))
"(defn cl-format
\"An implementation of a Common Lisp compatible format function\"
[stream format-in & args]
(let [compiled-format (if (string? format-in)
(compile-format format-in)
format-in)
navigator (init-navigator args)]
(execute-format stream compiled-format navigator)))
"
(with-out-str
(with-pprint-dispatch code-dispatch
(pprint
(defn pprint-defn [writer alis]
(if (next alis)
(let [[defn-sym defn-name & stuff] alis
[doc-str stuff] (if (string? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])
[attr-map stuff] (if (map? (first stuff))
[(first stuff) (next stuff)]
[nil stuff])]
(pprint-logical-block writer :prefix "(" :suffix ")"
(cl-format true "~w ~1I~@_~w" defn-sym defn-name)
(if doc-str
(cl-format true " ~_~w" doc-str))
(if attr-map
(cl-format true " ~_~w" attr-map))
;; Note: the multi-defn case will work OK for
12.55. TEST/TESTPRETTY.CLJ 1719
(defn tst-pprint
"A helper function to pprint to a string with a restricted right
margin"
[right-margin obj]
(binding [*print-right-margin* right-margin
*print-pretty* true]
(write obj :stream nil)))
(simple-tests pprint-datastructures-tests
(tst-pprint 20 future-filled)
#"#<Future@[0-9a-f]+: \r?\n 100>"
(tst-pprint 20 future-unfilled)
#"#<Future@[0-9a-f]+: \r?\n :pending>"
(tst-pprint 20 promise-filled)
#"#<Promise@[0-9a-f]+: \r?\n \(first\r?\n second\r?\n third\)>"
;; This hangs currently, cause we cant figure out whether a promise
;; is filled (tst-pprint 20 promise-unfilled)
;; #"#<Promise@[0-9a-f]+: \r?\n :pending>"
(tst-pprint 20 basic-agent)
#"#<Agent@[0-9a-f]+: \r?\n \(first\r?\n second\r?\n third\)>"
(tst-pprint 20 (failed-agent))
#"#<Agent@[0-9a-f]+ FAILED: \r?\n \"foo\">"
(tst-pprint 20 basic-atom)
#"#<Atom@[0-9a-f]+: \r?\n \(first\r?\n second\r?\n third\r?\)>"
(tst-pprint 20 basic-ref)
#"#<Ref@[0-9a-f]+: \r?\n \(first\r?\n second\r?\n third\)>"
(tst-pprint 20 delay-forced)
#"#<Delay@[0-9a-f]+: \r?\n \(first\r?\n second\r?\n third\)>"
;; Currently no way not to force the delay
;;(tst-pprint 20 delay-unforced)
#"#<Delay@[0-9a-f]+: \n :pending>"
;tpd i broke this
; (tst-pprint 20 (pprint-test-rec. first second third))
; "{:a first,\n :b second,\n :c third}"
(defmulti
test-dispatch
"A test dispatch method"
{:added "1.2" :arglists [[object]]}
#(and (seq %) (not (string? %))))
(simple-tests dispatch-tests
(with-pprint-dispatch test-dispatch
(with-out-str
(pprint ("hello" "there"))))
"[\"hello\" \"there\"]\n"
)
12.56 test1/examples.clj
test1/examples.clj
(ns clojure.test-clojure.protocols.examples)
(defprotocol ExampleProtocol
"example protocol used by clojure tests"
(definterface ExampleInterface
1722 CHAPTER 12. TEST/CLOJURE
12.57 test/moreexamples.clj
test/moreexamples.clj
(ns clojure.test-clojure.protocols.more-examples)
(defprotocol SimpleProtocol
"example protocol used by clojure tests. Note that
foo collides with examples/ExampleProtocol."
12.58 test/example.clj
test/example.clj
(ns clojure.test-clojure.repl.example)
-
Appendix A
AbstractMap
Callable
Collection
Comparable
Comparator
ConcurrentMap
DefaultHandler
Enumeration
IllegalArgumentException
InvocationHandler
Iterable
Iterator
List
Map
Map.Entry
Number
PushbackReader
RandomAccess
Runnable
Serializable
Set
URLClassLoader
1723
1724 APPENDIX A. EXTERNAL JAVA REFERENCES
Appendix B
Clojure License
1. DEFINITIONS
"Contribution" means:
where such changes and/or additions to the Program originate from and
are distributed by that particular Contributor. A Contribution
originates from a Contributor if it was added to the Program by such
Contributor itself or anyone acting on such Contributors
behalf. Contributions do not include additions to the Program which:
(i) are separate modules of software distributed in conjunction with
the Program under their own license agreement, and (ii) are not
derivative works of the Program.
1725
1726 APPENDIX B. COPYRIGHT AND LICENSES
2. GRANT OF RIGHTS
3. REQUIREMENTS
1727
iii) states that any provisions which differ from this Agreement are
offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
4. COMMERCIAL DISTRIBUTION
from claims, lawsuits and other legal actions brought by a third party
against the Indemnified Contributor to the extent caused by the acts
or omissions of such Commercial Contributor in connection with its
distribution of the Program in a commercial product offering. The
obligations in this section do not apply to any claims or Losses
relating to any actual or alleged intellectual property
infringement. In order to qualify, an Indemnified Contributor must: a)
promptly notify the Commercial Contributor in writing of such claim,
and b) allow the Commercial Contributor to control, and cooperate with
the Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such
claim at its own expense.
5. NO WARRANTY
6. DISCLAIMER OF LIABILITY
7. GENERAL
1729
This Agreement is governed by the laws of the State of New York and
the intellectual property laws of the United States of America. No
party to this Agreement will bring a legal action under this Agreement
more than one year after the cause of action arose. Each party waives
its rights to a jury trial in any resulting litigation.
1730 APPENDIX B. COPYRIGHT AND LICENSES
Clojure Copyright
; Copyright (c) Rich Hickey. All rights reserved. The use and
; distribution terms for this software are covered by the Eclipse
; Public License 1.0
; (http://opensource.org/licenses/eclipse-1.0.php) which can be
; found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be
; bound by the terms of this license. You must not remove this
; notice, or any other, from this software.
/***
* ASM: a very small and fast Java bytecode manipulation framework
* Copyright (c) 2000-2005 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
1731
*/
; Copyright (c) Chris Houser, Dec 2008. All rights reserved. The
; use and distribution terms for this software are covered by the
; Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
; which can be found in the file CPL.TXT at the root of this
; distribution. By using this software in any fashion, you are
; agreeing to be bound by the terms of this license. You must not
; remove this notice, or any other, from this software.
-
1732 APPENDIX B. COPYRIGHT AND LICENSES
Appendix C
tangle.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
1733
1734 APPENDIX C. BUILDING CLOJURE FROM THIS DOCUMENT
#include <sys/mman.h>
#include <fcntl.h>
#define DEBUG 0
/* handle begin{chunk}{chunkname} */
/* is this chunk name we are looking for? */
int foundchunk(int i, char *chunkname) {
if ((strncmp(&buffer[i+14],chunkname,strlen(chunkname)) == 0) &&
(buffer[i+13] == {) &&
(buffer[i+14+strlen(chunkname)] == })) return(1);
return(0);
}
/* handle end{chunk} */
/* is it really an end? */
int foundEnd(int i) {
if ((buffer[i] == \\) &&
(strncmp(&buffer[i+1],"end{chunk}",10) == 0)) {
return(1);
}
return(0);
}
/* handle getchunk{chunkname} */
/* is this line a getchunk? */
int foundGetchunk(int i, int linelen) {
int len;
C.2. THE TANGLE FUNCTION IN C 1735
if (strncmp(&buffer[i],"\\getchunk{",10) == 0) {
for(len=0; ((len < linelen) && (buffer[i+len] != })); len++);
return(len-10);
}
return(0);
}
/* memory map the input file into the global buffer and get the chunk */
int main(int argc, char *argv[]) {
int fd;
struct stat filestat;
if ((argc < 2) || (argc > 3)) {
perror("Usage: tangle filename chunkname");
exit(-1);
}
fd = open(argv[1],O_RDONLY);
if (fd == -1) {
perror("Error opening file for reading");
exit(-2);
}
if (fstat(fd,&filestat) < 0) {
perror("Error getting input file size");
exit(-3);
}
bufsize = (int)filestat.st_size;
buffer = mmap(0,filestat.st_size,PROT_READ,MAP_SHARED,fd,0);
if (buffer == MAP_FAILED) {
close(fd);
perror("Error reading the file");
exit(-4);
}
getchunk(argv[2]);
C.3. MAKEFILE 1737
close(fd);
return(0);
}
C.3 Makefile
Makefile
BOOK=clojure.pamphlet
WHERE=tpd
CLOJURE=${WHERE}/src/clj/clojure
CORE=${WHERE}//src/clj/clojure/core
JAVA=${WHERE}/src/clj/clojure/java
PPRINT=${WHERE}/src/clj/clojure/pprint
REFLECT=${WHERE}/src/clj/clojure/reflect
TEST=${WHERE}/src/clj/clojure/test
MAIN=${WHERE}/src/jvm/clojure
ASM=${WHERE}/src/jvm/clojure/asm
COMMONS=${WHERE}/src/jvm/clojure/asm/commons
LANG=${WHERE}/src/jvm/clojure/lang
TESTA=${WHERE}/test/clojure
TESTC=${WHERE}/test/clojure/test_clojure
TESTD=${WHERE}/test/clojure/test_clojure/annotations
TESTE=${WHERE}/test/clojure/test_clojure/genclass
TESTF=${WHERE}/test/clojure/test_clojure/java
TESTG=${WHERE}/test/clojure/test_clojure/pprint
TESTH=${WHERE}/test/clojure/test_clojure/protocols
TESTI=${WHERE}/test/clojure/test_clojure/repl
all:
rm -rf ${WHERE}
mkdir -p ${CLOJURE}
mkdir -p ${CORE}
mkdir -p ${JAVA}
mkdir -p ${PPRINT}
mkdir -p ${REFLECT}
mkdir -p ${TEST}
mkdir -p ${ASM}
mkdir -p ${COMMONS}
mkdir -p ${LANG}
mkdir -p ${TESTA}
mkdir -p ${TESTC}
mkdir -p ${TESTD}
mkdir -p ${TESTE}
1738 APPENDIX C. BUILDING CLOJURE FROM THIS DOCUMENT
mkdir -p ${TESTF}
mkdir -p ${TESTG}
mkdir -p ${TESTH}
mkdir -p ${TESTI}
tangle ${BOOK} build.xml >${WHERE}/build.xml
tangle ${BOOK} pom-template.xml >${WHERE}/pom-template.xml
tangle ${BOOK} protocols.clj >${CORE}/protocols.clj
tangle ${BOOK} core.clj >${CLOJURE}/core.clj
tangle ${BOOK} core_deftype.clj >${CLOJURE}/core_deftype.clj
tangle ${BOOK} core_print.clj >${CLOJURE}/core_print.clj
tangle ${BOOK} core_proxy.clj >${CLOJURE}/core_proxy.clj
tangle ${BOOK} data.clj >${CLOJURE}/data.clj
tangle ${BOOK} genclass.clj >${CLOJURE}/genclass.clj
tangle ${BOOK} gvec.clj >${CLOJURE}/gvec.clj
tangle ${BOOK} inspector.clj >${CLOJURE}/inspector.clj
tangle ${BOOK} browse.clj >${JAVA}/browse.clj
tangle ${BOOK} browse_ui.clj >${JAVA}/browse_ui.clj
tangle ${BOOK} io.clj >${JAVA}/io.clj
tangle ${BOOK} javadoc.clj >${JAVA}/javadoc.clj
tangle ${BOOK} shell.clj >${JAVA}/shell.clj
tangle ${BOOK} main.clj >${CLOJURE}/main.clj
tangle ${BOOK} parallel.clj >${CLOJURE}/parallel.clj
tangle ${BOOK} cl_format.clj >${PPRINT}/cl_format.clj
tangle ${BOOK} column_writer.clj >${PPRINT}/column_writer.clj
tangle ${BOOK} dispatch.clj >${PPRINT}/dispatch.clj
tangle ${BOOK} pprint_base.clj >${PPRINT}/pprint_base.clj
tangle ${BOOK} pretty_writer.clj >${PPRINT}/pretty_writer.clj
tangle ${BOOK} print_table.clj >${PPRINT}/print_table.clj
tangle ${BOOK} utilities.clj >${PPRINT}/utilities.clj
tangle ${BOOK} pprint.clj >${CLOJURE}/pprint.clj
tangle ${BOOK} java.clj >${REFLECT}/java.clj
tangle ${BOOK} reflect.clj >${CLOJURE}/reflect.clj
tangle ${BOOK} repl.clj >${CLOJURE}/repl.clj
tangle ${BOOK} set.clj >${CLOJURE}/set.clj
tangle ${BOOK} stacktrace.clj >${CLOJURE}/stacktrace.clj
tangle ${BOOK} string.clj >${CLOJURE}/string.clj
tangle ${BOOK} template.clj >${CLOJURE}/template.clj
tangle ${BOOK} junit.clj >${TEST}/junit.clj
tangle ${BOOK} tap.clj >${TEST}/tap.clj
tangle ${BOOK} test.clj >${CLOJURE}/test.clj
tangle ${BOOK} version.properties >${CLOJURE}/version.properties
tangle ${BOOK} walk.clj >${CLOJURE}/walk.clj
tangle ${BOOK} xml.clj >${CLOJURE}/xml.clj
tangle ${BOOK} zip.clj >${CLOJURE}/zip.clj
tangle ${BOOK} AnnotationVisitor.java \
>${ASM}/AnnotationVisitor.java
tangle ${BOOK} AnnotationWriter.java \
>${ASM}/AnnotationWriter.java
tangle ${BOOK} Attribute.java >${ASM}/Attribute.java
tangle ${BOOK} ByteVector.java >${ASM}/ByteVector.java
C.3. MAKEFILE 1739
>${TESTC}/other_functions.clj
tangle ${BOOK}
test/parallel.clj >${TESTC}/parallel.clj
tangle ${BOOK}
test/pprint.clj >${TESTC}/pprint.clj
tangle ${BOOK}
test/predicates.clj >${TESTC}/predicates.clj
tangle ${BOOK}
test/printer.clj >${TESTC}/printer.clj
tangle ${BOOK}
test/protocols.clj >${TESTC}/protocols.clj
tangle ${BOOK}
test/reader.clj >${TESTC}/reader.clj
tangle ${BOOK}
test/reflect.clj >${TESTC}/reflect.clj
tangle ${BOOK}
test/refs.clj >${TESTC}/refs.clj
tangle ${BOOK}
test/repl.clj >${TESTC}/repl.clj
tangle ${BOOK}
test/rt.clj >${TESTC}/rt.clj
tangle ${BOOK}
test/sequences.clj >${TESTC}/sequences.clj
tangle ${BOOK}
test/serialization.clj >${TESTC}/serialization.clj
tangle ${BOOK}
test/special.clj >${TESTC}/special.clj
tangle ${BOOK}
test/string.clj >${TESTC}/string.clj
tangle ${BOOK}
test/test.clj >${TESTC}/test.clj
tangle ${BOOK}
test/test_fixtures.clj >${TESTC}/test_fixtures.clj
tangle ${BOOK}
test/transients.clj >${TESTC}/transients.clj
tangle ${BOOK}
test/vars.clj >${TESTC}/vars.clj
tangle ${BOOK}
test/vectors.clj >${TESTC}/vectors.clj
tangle ${BOOK}
test/java_5.clj >${TESTD}/java_5.clj
tangle ${BOOK}
test/java_6_and_later.clj \
>${TESTD}/java_6_and_later.clj
tangle ${BOOK} test/examples.clj >${TESTE}/examples.clj
tangle ${BOOK} test/io.clj >${TESTF}/io.clj
tangle ${BOOK} test/javadoc.clj >${TESTF}/javadoc.clj
tangle ${BOOK} test/shell.clj >${TESTF}/shell.clj
tangle ${BOOK} test/test_cl_format.clj \
>${TESTG}/test_cl_format.clj
tangle ${BOOK} test1/test_helper.clj >${TESTG}/test_helper.clj
tangle ${BOOK} test/test_pretty.clj >${TESTG}/test_pretty.clj
tangle ${BOOK} test1/examples.clj >${TESTH}/examples.clj
tangle ${BOOK} test/more_examples.clj >${TESTH}/more_examples.clj
tangle ${BOOK} test/example.clj >${TESTI}/example.clj
( cd ${WHERE} ; ant )
latex clojure.pamphlet
makeindex clojure.idx
latex clojure.pamphlet
dvipdf clojure.dvi
xpdf clojure.pdf &
java -cp tpd/clojure.jar clojure.main
clean:
rm -f *.ilg
rm -f *.ind
rm -f *.out
rm -f *.aux
rm -f *.dvi
rm -f *.idx
rm -f *.log
1744 APPENDIX C. BUILDING CLOJURE FROM THIS DOCUMENT
rm -f *~
rm -f *.ps
rm -f *.tex
rm -f *.toc
rm -f *.pdf
Latex format files defines a newenvironment so that code chunks can be delim-
ited by \begin{chunk}{name} . . . \end{chunk} blocks. This is supported by the
following latex code.
So a trivial example of a literate latex file might look like
From a file called testcase that contains the above text we want to extract
the chunk names second chunk. We do this with:
which yields:
From the same file we might extract the chunk named first chunk. Notice
that this has the second chunk embedded recursively inside. So we execute:
which yields:
There is a third chunk called all which will extract both chunks:
1746 APPENDIX C. BUILDING CLOJURE FROM THIS DOCUMENT
which yields
The tangle function takes a third argument which is the name of an output file.
Thus, you can write the same results to a file with:
It is also worth noting that all chunks with the same name will be merged into
one chunk so it is possible to split chunks in mulitple parts and have them
extracted as one. That is,
will yield
\usepackage{verbatim}
Make the verbatim font smaller Note that we have to temporarily change the
@ to be just a character because the @font name uses it as a character
C.4. THE TANGLE FUNCTION IN CLOJURE 1747
\chardef\atcode=\catcode\@
\catcode\@=11
\renewcommand{\verbatim@font}{\ttfamily\small}
\catcode\@=\atcode
This declares a new environment named chunk which has one argument that is
the name of the chunk. All code needs to live between the \begin{chunk}{name}
and the \end{chunk} The name is used to define the chunk. Reuse of the same
chunk name later concatenates the chunks
For those of you who cant read latex this says: Make a new environment named
chunk with one argument The first block is the code for the \begin{chunk}{name}
The second block is the code for the \end{chunk} The % is the latex comment
character
We have two alternate markers, a lightweight one using dashes and a heavy-
weight one using the \begin and \end syntax You can choose either one by
changing the comment char in column 1
This declares the place where we want to expand a chunk Technically we dont
need this because a getchunk must always be properly nested within a chunk
and will be verbatim.
\providecommand{\getchunk}[1]{%
\noindent%
{\small $\backslash{}$begin\{chunk\}\{{\bf #1}\}}}% mark the reference
C.4.4 Imports
Clojure tangle
\begin{chunk}{name}
... (code for name)...
\end{chunk}
Clojure tangle
Clojure tangle
C.4. THE TANGLE FUNCTION IN CLOJURE 1749
Clojure tangle
(defn ischunk [line]
^{:doc "Find chunks delimited by latex syntax"}
(let [ begin #"^\\begin\{chunk\}\{.*\}$"
end #"^\\end\{chunk\}$"
get #"^\\getchunk\{.*\}$"
trimmed (.trim line) ]
(cond
(re-find begin trimmed)
(list define (apply str (butlast (drop 14 trimmed))))
(re-find end trimmed)
(list end nil)
(re-find get trimmed)
(list refer trimmed)
:else
(list nil trimmed))))
-
1750 APPENDIX C. BUILDING CLOJURE FROM THIS DOCUMENT
define == parse the chunkname and start gathering lines onto a stack
end == push the completed list of lines into a stack of chunks already in
the hash table
otherwise == if we are gathering, push the line onto the stack
each of the sublists is a set of lines in reverse (stack) order each sublist is a single
chunk of lines. There is a new sublist for each reuse of the same chunkname
Calls to ischunk can have 4 results (define, end, refer, nil) where
The variable gather is initially false, implying that we are not gathering code.
The variable gather is true if we are gathering a chunk.
Clojure tangle
Clojure tangle
given the input filename. then we recursively expand the topchunk to the
output stream
Clojure tangle
(defn tangle
^{:doc "Extract the source code from a pamphlet file,
optional file output"}
([filename topchunk] (tangle filename topchunk nil))
([filename topchunk file]
(if (string? file)
(with-open [where (BufferedWriter. (FileWriter. file))]
(expand topchunk where (hashchunks (read-file filename))))
(expand topchunk nil (hashchunks (read-file filename))))))
-
Bibliography
1753
1754 BIBLIOGRAPHY
aphyr.com/posts/301-clojure-from-the-ground-up-welcome
aphyr.com/posts/302-clojure-from-the-ground-up-basic-types
[Web11] Web
Clojures unconventional symbols
arcanesentiment.blogspot.com/2011/01/clojures-unconventional-symbols.html
[Wiki] Wikipedia
Red-black tree
en.wikipedia.org/wiki/Red-black_tree
[Wiki1] Wikipedia
The ML programming language
en.wikipedia.org/wiki/ML_(programming language)
1755
1756 BIBLIOGRAPHY
extended by 44, 44
extended by 43, 43
extends 41, 42
black, 4245
Black, class in 1000, 42
black, method in 1000, 45
BlackBranch
extended by 45, 45
extends 42, 44
BlackBranch, class in 1000, 44
BlackBranchVal
extends 44, 45
BlackBranchVal, class in 1000, 45
BlackVal
extends 42, 43
BlackVal, class in 1000, 43
Boolean, 63
Class, 63
Box, 585
Class, 585
ByteVector, 176
Class, 176
Callable, 1723
extended by 774, 774
Interface, 1723
CancellationException, 97
Character, 63
Class, 63
CharacterReader, 71
extends 509, 81
CharacterReader, class in 825, 81
ChunkBuffer, 586
implements 768, 586
Class, 586
ChunkedCons, 586
extends 571, 586
implements 773, 586
Class, 586
Class
AbstractMap, 1723
AdviceAdapter, 413
AFn, 509
AFunction, 519
1760 BIBLIOGRAPHY
Agent, 520
AMapEntry, 527
AnalyzerAdapter, 426
AnnotationWriter, 165
APersistentMap, 530
APersistentSet, 538
APersistentVector, 541
ARef, 553
AReference, 552
ArgReader in 825, 82
ArityException, 556
ArrayChunk, 556
ArrayNode in 969, 955
ArraySeq, 558
ASeq, 571
Atom, 577
ATransientMap, 579
ATransientSet, 581
Attribute, 171
BigDecimal, 63
BigInt, 582
BigInteger, 63
Binding, 585
BitmapIndexedNode in 969, 959
Black in 1000, 42
BlackBranch in 1000, 44
BlackBranchVal in 1000, 45
BlackVal in 1000, 43
Boolean, 63
Box, 585
ByteVector, 176
Character, 63
CharacterReader in 825, 81
ChunkBuffer, 586
ChunkedCons, 586
ClassAdapter, 183
ClassReader, 185
ClassWriter, 233
CodeSizeEvaluator, 445
CommentReader in 825, 74
Compile, 588
Compiler, 590
Cons, 766
DefaultHandler, 1723
Delay, 768
DiscardReader in 825, 89
BIBLIOGRAPHY 1761
DispatchReader in 825, 83
Double, 63
DynamicClassLoader, 769
Edge, 262
EmptyVisitor, 449
EnumerationSeq, 770
EvalReader in 825, 87
FieldWriter, 264
FnReader in 825, 86
Frame, 269
GeneratorAdapter, 453
Handler, 300
HashCollisionNode in 969, 965
IllegalArgumentException, 1723
Item, 301
IteratorSeq, 806
KeyIterator in 1000, 103
Keyword, 810
KeywordLookupSite, 816
Label, 305
LazilyPersistentVector, 817
LazySeq, 818
LineNumberingPushbackReader, 823
LispReader, 825
ListReader in 825, 80
LocalVariablesSorter, 484
LockingTransaction, 836
Long, 63
main, 1167
MapEntry, 850
MapReader in 825, 81
MetaReader in 825, 75
Method, 491
MethodAdapter, 314
MethodImplCache, 851
MethodWriter, 326
MultiFn, 852
Namespace, 861
Node, 41
NodeIterator in 1000, 103
Number, 1723
Obj, 947
Pattern, 63
PersistentArrayMap, 948
PersistentHashMap, 969
PersistentHashSet, 980
1762 BIBLIOGRAPHY
PersistentList, 982
PersistentQueue, 989
PersistentStructMap, 995
PersistentTreeMap, 1000
PersistentTreeSet, 1012
PersistentVector, 1014
ProxyHandler, 1030
PushbackReader, 1723
Range, 1031
Ratio, 1033
Red in 1000, 46
RedBranch in 1000, 48
RedBranchVal in 1000, 49
RedVal in 1000, 47
Ref, 1034
Reflector, 1044
RegexReader in 825, 85
Repl, 1054
RestFn, 1055
RT, 1094
Script, 1138
Seq in 1000, 60
SeqEnumeration, 1138
SeqIterator, 1139
SerialVersionUIDAdder, 496
SetReader in 825, 87
StaticInitMerger, 506
String, 63
StringReader in 825, 72
StringSeq, 1141
Symbol, 1143
SyntaxQuoteReader in 825, 76
TransactionalHashMap, 1145
Type, 394
UnmatchedDelimiterReader in 825, 80
UnquoteReader in 825, 79
UnreadableReader in 825, 89
URLClassLoader, 1723
Util, 1149
ValIterator in 1000, 104
Var, 1152
VarReader in 825, 85
VectorReader in 825, 80
WrappingReader in 825, 74
XMLHandler, 1164
BIBLIOGRAPHY 1763
ClassAdapter, 183
extended by 496, 496
extended by 506, 506
implements 229, 183
Class, 183
ClassReader, 185
Class, 185
ClassVisitor, 229
implemented by 183, 183
implemented by 233, 233
implemented by 449, 449
Interface, 229
ClassWriter, 233
implements 229, 233
Class, 233
CodeSizeEvaluator, 445
extends 314, 445
implements 387, 445
Class, 445
Collection, 1723
implemented by 538, 538
implemented by 989, 989
Interface, 1723
CommentReader, 71, 84
extends 509, 74
CommentReader, class in 825, 74
Comparable, 1723
implemented by 541, 541
implemented by 810, 810
implemented by 1033, 1033
implemented by 1034, 1034
implemented by 1143, 1143
Interface, 1723
Comparator, 1723
implemented by 519, 519
Interface, 1723
Compile, 588
Class, 588
Compiler, 590
implements 387, 590
Class, 590
ConcurrentMap, 1723
implemented by 1145, 1145
Interface, 1723
1764 BIBLIOGRAPHY
Cons, 766
extends 571, 766
implements 1723, 766
Class, 766
Counted, 100, 768
extended by 801, 801
extended by 802, 802
extended by 808, 808
extended by 809, 809
extended by 799, 799
extended by 799, 799
implemented by 586, 586
implemented by 982, 982
implemented by 989, 989
implemented by 1031, 1031
Interface, 768
DefaultHandler, 1723
extended by 1164, 1164
Class, 1723
Delay, 768
implements 773, 768
Class, 768
DiscardReader, 84
extends 509, 89
DiscardReader, class in 825, 89
Dispatch Macro Table, 71, 84
dispatchMacros, 83, 84
DispatchReader, 72, 83
extends 509, 83
DispatchReader, class in 825, 83
Double, 63
Class, 63
DynamicClassLoader, 769
extends 1723, 769
Class, 769
Edge, 262
Class, 262
EmptyVisitor, 449
implements 163, 449
implements 229, 449
implements 263, 449
implements 317, 449
Class, 449
BIBLIOGRAPHY 1765
Enumeration, 1723
implemented by 1138, 1138
Interface, 1723
EnumerationSeq, 770
extends 571, 770
Class, 770
equals, method in 1143, 69
EvalReader, 84
extends 509, 87
EvalReader, class in 825, 87
exceptions, 95, 97
Extends
AbstractMap, by TransactionalHashMap, 1145
AFn, by AFunction, 519
AFn, by APersistentMap, 530
AFn, by APersistentSet, 538
AFn, by APersistentVector, 541
AFn, by ArgReader, 82
AFn, by ATransientMap, 579
AFn, by ATransientSet, 581
AFn, by CharacterReader, 81
AFn, by CommentReader, 74
AFn, by DiscardReader, 89
AFn, by DispatchReader, 83
AFn, by EvalReader, 87
AFn, by FnReader, 86
AFn, by ListReader, 80
AFn, by MapReader, 81
AFn, by MetaReader, 75
AFn, by MultiFn, 852
AFn, by RegexReader, 85
AFn, by SetReader, 87
AFn, by StringReader, 72
AFn, by Symbol, 1143
AFn, by SyntaxQuoteReader, 76
AFn, by UnmatchedDelimiterReader, 80
AFn, by UnquoteReader, 79
AFn, by UnreadableReader, 89
AFn, by VarReader, 85
AFn, by VectorReader, 80
AFn, by WrappingReader, 74
AFunction, by RestFn, 1055
AMapEntry, by MapEntry, 850
AMapEntry, by Node, 41
APersistentMap, by PersistentStructMap, 995
1766 BIBLIOGRAPHY
FieldVisitor, 263
implemented by 449, 449
implemented by 264, 264
Interface, 263
FieldWriter, 264
implements 263, 264
Class, 264
find, method in 955, 58
Fn, 772
1768 BIBLIOGRAPHY
GeneratorAdapter, 453
extended by 413, 413
extends 484, 453
Class, 453
Handler, 300
Class, 300
HashCollisionNode, 59
implements 58, 965
HashCollisionNode, class in 969, 965
IChunk, 772
extends 799, 772
implemented by 556, 556
Interface, 772
IChunkedSeq, 773
extends 805, 773
implemented by 586, 586
Interface, 773
IDeref, 773
extended by 805, 805
implemented by 768, 768
Interface, 773
IEditableCollection, 774
implemented by 948, 948
implemented by 969, 969
implemented by 980, 980
implemented by 1014, 1014
Interface, 774
IFn, 71, 84, 774
extends 1723, 774
extends 1723, 774
implemented by 509, 509
implemented by 810, 810
implemented by 1034, 1034
implemented by 1152, 1152
Interface, 774
BIBLIOGRAPHY 1769
Interface, 799
IndexedSeq, 799
extends 768, 799
extends 805, 799
implemented by 558, 558
implemented by 1141, 1141
Interface, 799
INode, 58
extends 1723, 58
implemented by 955, 955
implemented by 959, 959
implemented by 965, 965
Interface, 58
Interface
AnnotationVisitor, 163
Associative, 576
Callable, 1723
ClassVisitor, 229
Collection, 1723
Comparable, 1723
Comparator, 1723
ConcurrentMap, 1723
Counted, 768
Enumeration, 1723
FieldVisitor, 263
Fn, 772
IChunk, 772
IChunkedSeq, 773
IDeref, 773
IEditableCollection, 774
IFn, 774
IKeywordLookup, 796
ILookup, 797
ILookupSite, 797
ILookupThunk, 798
IMapEntry, 798
IMeta, 799
Indexed, 799
IndexedSeq, 799
INode, 58
InvocationHandler, 1723
IObj, 800
IPersistentCollection, 800
IPersistentList, 801
IPersistentMap, 801
BIBLIOGRAPHY 1773
IPersistentSet, 802
IPersistentStack, 802
IPersistentVector, 802
IPromiseImpl, 803
IProxy, 803
IReduce, 804
IRef, 805
IReference, 804
ISeq, 805
Iterable, 1723
Iterator, 1723
ITransientAssociative, 807
ITransientCollection, 808
ITransientMap, 808
ITransientSet, 809
ITransientVector, 809
List, 1723
Map, 1723
Map.Entry, 1723
MapEquivalence, 850
MethodVisitor, 317
Named, 861
Numbers, 867
Opcodes, 387
RandomAccess, 1723
Reversible, 1094
Runnable, 1723
Seqable, 1140
Sequential, 1140
Serializable, 1723
Set, 1723
Settable, 1140
Sorted, 1141
TableSwitchGenerator, 508
intern(2), method in 1143, 68
intern, method in 1143, 67
InvocationHandler, 1723
implemented by 1030, 1030
Interface, 1723
IObj, 100, 800
extends 799, 800
implemented by 519, 519
implemented by 947, 947
implemented by 948, 948
implemented by 969, 969
1774 BIBLIOGRAPHY
Interface, 803
IProxy, 803
Interface, 803
IReduce, 804
implemented by 558, 558
implemented by 982, 982
implemented by 1031, 1031
Interface, 804
IRef, 805
extends 773, 805
implemented by 553, 553
implemented by 1034, 1034
implemented by 1152, 1152
Interface, 805
IReference, 804
extends 799, 804
implemented by 552, 552
Interface, 804
ISeq, 805
extended by 773, 773
extended by 799, 799
extends 800, 805
extends 1140, 805
implemented by 571, 571
implemented by 818, 818
Interface, 805
Item, 301
Class, 301
Iterable, 1723
extended by 801, 801
implemented by 530, 530
implemented by 541, 541
Interface, 1723
Iterator, 1723
implemented by 103, 103
implemented by 103, 103
implemented by 1139, 1139
implemented by 104, 104
Interface, 1723
Iterators, 55
IteratorSeq, 806
extends 571, 806
Class, 806
ITransientAssociative, 807
extended by 808, 808
1776 BIBLIOGRAPHY
Class, 63
main, 1167
Class, 1167
Makefile, i, ii, 1737
Map, 1723
implemented by 530, 530
Interface, 1723
Map.Entry, 1723
extended by 798, 798
Interface, 1723
MapEntry, 850
extends 527, 850
Class, 850
MapEquivalence, 850
implemented by 530, 530
Interface, 850
MapReader, 71
extends 509, 81
MapReader, class in 825, 81
mask, method in 969, 57
MetaReader, 71, 84
extends 509, 75
MetaReader, class in 825, 75
Method, 491
Class, 491
MethodAdapter, 314
extended by 426, 426
extended by 445, 445
extended by 484, 484
implements 317, 314
Class, 314
MethodImplCache, 851
Class, 851
MethodVisitor, 317
implemented by 449, 449
implemented by 314, 314
implemented by 326, 326
Interface, 317
MethodWriter, 326
implements 317, 326
Class, 326
MultiFn, 852
extends 509, 852
Class, 852
BIBLIOGRAPHY 1779
Named, 861
implemented by 810, 810
implemented by 1143, 1143
Interface, 861
Namespace, 861
extends 552, 861
implements 1723, 861
Class, 861
Node, 41
extended by 42, 42
extended by 46, 46
extends 527, 41
Class, 41
NodeIterator
implements 1723, 103
NodeIterator, class in 1000, 103
Number, 1723
extended by 582, 582
extended by 1033, 1033
Class, 1723
Numbers, 867
Interface, 867
Obj, 947
extended by 571, 571
extended by 818, 818
extended by 989, 989
implements 800, 947
implements 1723, 947
Class, 947
Opcodes, 387
implemented by 413, 413
implemented by 445, 445
implemented by 590, 590
Interface, 387
Pattern, 63
Class, 63
PersistentArrayMap, 948
implements 774, 948
implements 800, 948
Class, 948
PersistentHashMap, 57, 969
implements 774, 969
implements 800, 969
Class, 969
1780 BIBLIOGRAPHY
PersistentHashMap.mask, 57
PersistentHashMap$ArrayNode, 955
PersistentHashMap$BitmapIndexedNode, 959
PersistentHashMap$HashCollisionNode, 965
PersistentHashSet, 980
extends 538, 980
implements 774, 980
implements 800, 980
Class, 980
PersistentList, 982
extends 571, 982
implements 768, 982
implements 801, 982
implements 804, 982
implements 1723, 982
Class, 982
PersistentQueue, 989
extends 947, 989
implements 1723, 989
implements 768, 989
implements 801, 989
Class, 989
PersistentStructMap, 995
extends 530, 995
implements 800, 995
Class, 995
PersistentTreeMap, 55, 1000
extends 530, 1000
extends 571, 60
implements 800, 1000
implements 1094, 1000
implements 1141, 1000
Class, 1000
PersistentTreeMap.balanceLeftDel, 53
PersistentTreeMap.balanceRightDel, 52
PersistentTreeMap.black, 45
PersistentTreeMap.leftBalance, 54
PersistentTreeMap.red, 50
PersistentTreeMap.remove, 1000
PersistentTreeMap.removeRight, 42
PersistentTreeMap.replace, 55
PersistentTreeMap.rightBalance, 54
PersistentTreeMap$Black, 42
PersistentTreeMap$BlackBranch, 44
BIBLIOGRAPHY 1781
PersistentTreeMap$BlackBranchVal, 45
PersistentTreeMap$BlackVal, 43
PersistentTreeMap$KeyIterator, 103
PersistentTreeMap$NodeIterator, 103
PersistentTreeMap$Red, 46
PersistentTreeMap$RedBranch, 48
PersistentTreeMap$RedBranchVal, 49
PersistentTreeMap$RedVal, 47
PersistentTreeMap$Seq, 60
PersistentTreeMap$ValIterator, 104
PersistentTreeSet, 1012
extends 538, 1012
implements 800, 1012
implements 1094, 1012
implements 1141, 1012
Class, 1012
PersistentVector, 1014
extends 541, 1014
implements 774, 1014
implements 800, 1014
Class, 1014
promise, 95
ProxyHandler, 1030
implements 1723, 1030
Class, 1030
PushbackReader, 1723
extended by 823, 823
Class, 1723
RandomAccess, 1723
implemented by 541, 541
Interface, 1723
Range, 1031
extends 571, 1031
implements 768, 1031
implements 804, 1031
Class, 1031
Ratio, 1033
extends 1723, 1033
implements 1723, 1033
Class, 1033
read, method in 825, 69
Red
extended by 48, 48
1782 BIBLIOGRAPHY
extended by 47, 47
extends 41, 46
red, 4649
Red, class in 1000, 46
red, method in 1000, 50
RedBranch
extended by 49, 49
extends 46, 48
RedBranch, class in 1000, 48
RedBranchVal
extends 48, 49
RedBranchVal, class in 1000, 49
RedVal
extends 46, 47
RedVal, class in 1000, 47
Ref, 1034
extends 553, 1034
implements 1723, 1034
implements 774, 1034
implements 805, 1034
Class, 1034
Reflector, 1044
Class, 1044
RegexReader, 84
extends 509, 85
RegexReader, class in 825, 85
remove, 52
remove, method in 1000, 1000
removeRight, 52
removeRight, method in 1000, 42
Repl, 1054
Class, 1054
replace, method in 1000, 55
RestFn, 1055
extends 519, 1055
Class, 1055
Reversible, 1094
extended by 802, 802
implemented by 1000, 1000
implemented by 1012, 1012
Interface, 1094
rightBalance, method in 1000, 54
RT, 1094
Class, 1094
BIBLIOGRAPHY 1783
Runnable, 1723
extended by 774, 774
Interface, 1723
Script, 1138
Class, 1138
Seq, class in 1000, 60
Seqable, 100, 1140
extended by 800, 800
Interface, 1140
SeqEnumeration, 1138
implements 1723, 1138
Class, 1138
SeqIterator, 1139
implements 1723, 1139
Class, 1139
Seqs, 55
Sequential, 1140
extended by 801, 801
extended by 802, 802
extended by 805, 805
Interface, 1140
Serializable, 1723
extended by 58, 58
implemented by 519, 519
implemented by 530, 530
implemented by 538, 538
implemented by 541, 541
implemented by 571, 571
implemented by 556, 556
implemented by 766, 766
implemented by 810, 810
implemented by 861, 861
implemented by 947, 947
implemented by 1143, 1143
Interface, 1723
SerialVersionUIDAdder, 496
extends 183, 496
Class, 496
Set, 1723
implemented by 538, 538
Interface, 1723
SetReader, 84
extends 509, 87
SetReader, class in 825, 87
1784 BIBLIOGRAPHY
Settable, 1140
implemented by 1152, 1152
Interface, 1140
Sorted, 1141
implemented by 1000, 1000
implemented by 1012, 1012
Interface, 1141
StaticInitMerger, 506
extends 183, 506
Class, 506
String, 63
Class, 63
StringReader, 71
extends 509, 72
StringReader, class in 825, 72
StringSeq, 1141
extends 571, 1141
implements 799, 1141
Class, 1141
Symbol, 1143
extends 509, 1143
implements 1723, 1143
implements 800, 1143
implements 861, 1143
implements 1723, 1143
Class, 1143
Symbol.equals, 69
Symbol.intern, 67
Symbol.intern(2), 68
Symbol.toString, 68
Syntax Macro Table, 72, 83
SyntaxQuoteReader, 71
extends 509, 76
SyntaxQuoteReader, class in 825, 76
TableSwitchGenerator, 508
Interface, 508
tangle.c, i, 1733
toString, method in 1143, 68
TransactionalHashMap, 1145
extends 1723, 1145
implements 1723, 1145
Class, 1145
Type, 394
BIBLIOGRAPHY 1785
Class, 394
UnmatchedDelimiterReader, 71
extends 509, 80
UnmatchedDelimiterReader, class in 825, 80
UnquoteReader, 71
extends 509, 79
UnquoteReader, class in 825, 79
UnreadableReader, 84
extends 509, 89
UnreadableReader, class in 825, 89
URLClassLoader, 1723
extended by 769, 769
Class, 1723
Util, 1149
Class, 1149
ValIterator
implements 1723, 104
ValIterator, class in 1000, 104
Var, 1152
extends 553, 1152
implements 774, 1152
implements 805, 1152
implements 1140, 1152
Class, 1152
VarReader, 8385
extends 509, 85
VarReader, class in 825, 85
VectorReader, 71
extends 509, 80
VectorReader, class in 825, 80
WrappingReader, 71
extends 509, 74
WrappingReader, class in 825, 74
XMLHandler, 1164
extends 1723, 1164
Class, 1164