├── CHANGES.md ├── Makefile ├── README ├── config.dist ├── configure ├── editors ├── ocamltype.vim └── textmate.txt ├── src ├── Makefile ├── annot.nw ├── annot.pod ├── custom_unix_stubs.c ├── intervalmap.nw ├── main.nw ├── position.nw ├── syntax.nw ├── test.in └── test.ref └── tools └── nofake /CHANGES.md: -------------------------------------------------------------------------------- 1 | 1.1.0 (19 Jun 2015): 2 | * If the `annot` file cannot be found, just output the 3 | fully qualified pathname (#8 from Cedric Cellier). 4 | * Add `opam` file for local pinning workflow. 5 | 6 | 1.0.0 (02 Sep 2012): 7 | * Initial public release. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is the top-level Makefile. Nothing interesting happens here; for 3 | # every target we change to src/ first and build it there. 4 | # 5 | 6 | all: config.mk 7 | $(MAKE) -C src $@ 8 | 9 | clobber: config.mk 10 | $(MAKE) -C src $@ 11 | rm -f config.mk 12 | 13 | %: config.mk 14 | $(MAKE) -C src $@ 15 | 16 | 17 | config.mk: 18 | @echo "Have you run ./configure? ./config.mk is missing" 19 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | == Annot 1.1.0 == 3 | 4 | This is the source code for Annot, a tool to lookup annotations for 5 | Objective Caml source code. The main purpose is to lookup the type 6 | annotation for a given identifier in an OCaml source file from within an 7 | editor. Annot works by taking the line and column of the identifier 8 | and looking up the information in an annotations file produced by the 9 | OCaml compiler: 10 | 11 | $ annot -type 57 5 main.annot 12 | string -> (in_channel -> 'a) -> 'a 13 | 14 | The code above looks up the type annotation for an identifier in 15 | main.ml, at line 57, column 5. The invocation of this command is 16 | typically bound to a key in an editor rather than invoked explicitly in 17 | a shell. For full documentation, please refer to the manual page 18 | annot.pod. 19 | 20 | OCaml 3.09 introduced the -dtypes compiler flag that causes the compiler 21 | to write annotation files. Hence, invoking compiler runs with -dtypes 22 | keeps the annotation files up to date. Currently the compiler only 23 | generates type annotations; however, in principle other annotations 24 | could be looked up as well. 25 | 26 | INSTALLATION 27 | 28 | Annot is implemented in Objective Caml. To compile it from source code, 29 | try the following: 30 | 31 | ./configure --prefix=/usr/local 32 | make 33 | make install 34 | 35 | The configure script tries to locate all necessary tools and generates a 36 | file config.mk that is included by the main Makefile. If something goes 37 | wrong, try running "./configure -v". 38 | 39 | The latest tested version is OCaml 3.11.0 and 3.11.1. 40 | 41 | USING ANNOT FROM AN EDITOR 42 | 43 | To use Annot from Vim, include the following code into your ~/.vimrc 44 | file (also provided in editors/): 45 | 46 | function! OCamlType() 47 | let col = col('.') 48 | let line = line('.') 49 | let file = expand("%:p:r") 50 | echo system("annot -n -type ".line." ".col." ".file.".annot") 51 | endfunction 52 | map ,t :call OCamlType() 53 | 54 | The key combination ",t" (without quotes) looks up the type annotation 55 | for the identifier under the cursor. 56 | 57 | Instructions for other editors can be found in the editors/ directory. 58 | 59 | SOURCE CODE 60 | 61 | The source code is a literate program for the NoWeb literate programming 62 | tools and resides in the *.nw file in directory src/. However, you don't 63 | need NoWeb to compile and install the Annot. The configure detects when 64 | NoWeb is installed otherwise uses Perl script tools/nofake to extract 65 | the Objective Caml code code from the *.nw files. 66 | 67 | If you have the NoWeb tool chain installs you can use make to produce 68 | PDF files from *.nw files by executing "make main.pdf", for example. 69 | Note that the Nofake tool that comes as part of the Annot source code is 70 | not enough to build the PDF documentation. For this you need the real 71 | NoWeb tools. These are available on Debian as package "nowebm". 72 | 73 | COPYRIGHT 74 | 75 | Please see the manual page annot.pod for the exact copyright. 76 | 77 | AUTHOR AND CONTACT 78 | 79 | Anil Madhavapeddy 80 | anil@recoil.org 81 | http://anil.recoil.org 82 | 83 | Christian Lindig (original author) 84 | lindig@cs.uni-sb.de 85 | http://www.cs.uni-sb.de/~lindig/ 86 | 87 | -------------------------------------------------------------------------------- /config.dist: -------------------------------------------------------------------------------- 1 | # required tools and settings 2 | BINEXT := opt 3 | DATE := 20060516 4 | LIBEXT := cmxa 5 | NAME := annot 6 | NOTANGLE := notangle 7 | OCAML_VERSION := 3.09 8 | OCAMLC := ocamlc 9 | OCAMLDEP := ocamldep 10 | OCAMLLEX := ocamllex 11 | OCAMLYACC := ocamlyacc 12 | POD2MAN := pod2man 13 | PREFIX := /usr/local 14 | # optional tools and settings 15 | LATEX := pdflatex 16 | NOWEAVE := noweave 17 | OCAMLOPT := ocamlopt.opt 18 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl -w 2 | # configuration script for OCaml project 3 | # run as ./configure 4 | 5 | use strict; 6 | 7 | my $name = "annot"; 8 | my $config = "config.mk"; # results go here 9 | my $verbose = 0; # verbosity of ./configure 10 | my $fatal = 0; # fatal-error flag 11 | my %req = (); # required tools or settings 12 | my %opt = (); # optional tools or settings 13 | 14 | # ------------------------------------------------------------------ 15 | # sanity check 16 | 17 | if (!-d "./tools" || !-f "README") { 18 | (my $msg = <&1` =~ /(\d\.\d\d)/ 57 | && $1 58 | || ""; 59 | chop($req{date} =`date +%Y%m%d`) || die "cannot run date(1): $!"; 60 | 61 | 62 | # ------------------------------------------------------------------ 63 | # find optional tools and settings 64 | 65 | $opt{pdflatex} = search("pdflatex",@path); 66 | $opt{noweave} = search("noweave",@path); 67 | 68 | # ------------------------------------------------------------------ 69 | # report some results 70 | 71 | (my $msg = <',$config) || die "$!"; 157 | print OUT "# required tools and settings\n"; 158 | foreach my $key (sort (keys %req)) { 159 | (my $k = $key) =~ tr [a-z] [A-Z]; 160 | printf OUT "%-20s := %s\n",$k,$req{$key}; 161 | $verbose && printf "%-20s := %s\n",$k,$req{$key}; 162 | } 163 | 164 | print OUT "# optional tools and settings\n"; 165 | foreach my $key (sort (keys %opt)) { 166 | (my $k = $key) =~ tr [a-z] [A-Z]; 167 | printf OUT "%-20s := %s\n",$k,$opt{$key}; 168 | $verbose && printf "%-20s := %s\n",$k,$opt{$key}; 169 | } 170 | close(OUT) || die "cannot close file: $!"; 171 | print "Configuration successfully written to $config.\n"; 172 | exit(0); 173 | 174 | # ------------------------------------------------------------------ 175 | # search (file, dir, dir, dir, ..), call as search(file, @dirs) search 176 | # for file in dirs and return, full path, if found, and "" otherwise. 177 | # 178 | 179 | sub search { 180 | my $file = shift (@_); 181 | 182 | $verbose && printf( "searching for %-20s", $file); 183 | while (my $dir = shift (@_)) { 184 | my $x = "$dir/$file"; 185 | if (-f $x) { 186 | $verbose && print "found $x\n"; 187 | return $x 188 | } 189 | } 190 | $verbose && print "not found\n"; 191 | return ""; 192 | } 193 | 194 | # ------------------------------------------------------------------ 195 | # usage 196 | 197 | sub usage { 198 | (my $msg = < 12 | 13 | 14 | -------------------------------------------------------------------------------- /editors/textmate.txt: -------------------------------------------------------------------------------- 1 | Configuration for TextMate provided by Bruno De Fraine 2 | : 3 | 4 | Configure the following as a command for the Ocaml scope (source.ocaml), 5 | no input, output as a tool tip: 6 | 7 | exec annot -n -type $TM_LINE_NUMBER $TM_LINE_INDEX "${TM_FILEPATH%.*}.annot" 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # vim:ts=8 sw=4 noet: 2 | # 3 | 4 | TOP := .. 5 | include $(TOP)/config.mk 6 | 7 | PATH := $(PATH):$(TOP)/tools 8 | 9 | INCLUDES := -I . 10 | OCAMLC_FLAGS := -g -dtypes $(INCLUDES) 11 | OCAMLOPT_FLAGS := -p -dtypes $(INCLUDES) 12 | OCAMLOPT_FLAGS := -dtypes $(INCLUDES) 13 | 14 | BINDIR := $(PREFIX)/bin 15 | MAN1DIR := $(PREFIX)/man/man1 16 | 17 | # -- high-level targets 18 | .PHONY: all doc clean pdf test install clobber 19 | 20 | all: load.ml $(NAME).$(BINEXT) 21 | 22 | clean: 23 | rm -f *.cmx *.cmo *.cmi *.o *.ml *.mli quest.opt quest.byte 24 | rm -f *.log *.pdf *.aux *.inc *.tex *.out 25 | rm -f *.mll *.mly 26 | rm -f DEPEND 27 | rm -f load.ml *.annot 28 | rm -f pod2* 29 | rm -f test.out 30 | 31 | clobber: clean 32 | rm -f $(NAME).byte $(NAME).opt 33 | rm -f $(NAME).1 34 | rm -f config.mk 35 | 36 | install: $(NAME).$(BINEXT) $(NAME).1 $(BINDIR) $(MAN1DIR) 37 | cp $(NAME).$(BINEXT) $(BINDIR)/$(NAME) 38 | cp $(NAME).1 $(MAN1DIR) 39 | 40 | 41 | test: test.annot test.in 42 | rm -f test.out 43 | while read LINE COL; do \ 44 | ./$(NAME).$(BINEXT) -type $$LINE $$COL test.annot >> test.out;\ 45 | done < test.in 46 | diff test.ref test.out 47 | 48 | $(BINDIR): 49 | mkdir -p $@ 50 | 51 | $(MAN1DIR): 52 | mkdir -p $@ 53 | 54 | 55 | # -- important files 56 | 57 | ML := \ 58 | intervalmap.ml \ 59 | annot.ml \ 60 | parser.ml \ 61 | scanner.ml \ 62 | main.ml \ 63 | 64 | 65 | MLI := $(patsubst %.ml, %.mli, $(ML)) 66 | CMO := $(patsubst %.ml, %.cmo, $(ML)) 67 | CMX := $(patsubst %.ml, %.cmx, $(ML)) 68 | OBJ := $(patsubst %.ml, %.o , $(ML)) 69 | 70 | TEX := annot.tex intervalmap.tex main.tex \ 71 | position.tex syntax.tex 72 | INC := $(patsubst %.tex, %.inc, $(TEX)) 73 | PDF := $(patsubst %.tex, %.pdf, $(TEX)) 74 | 75 | # -- 76 | 77 | cmo: $(CMO) 78 | cmi: $(CMI) 79 | obj: $(OBJ) 80 | 81 | # -- 82 | 83 | LIBCMO := 84 | LIBCMX := 85 | 86 | # -- binaries 87 | 88 | $(NAME).byte: $(CMO) $(LIBCMO) custom_unix_stubs.c 89 | $(OCAMLC) $(OCAMLC_FLAGS) custom_unix_stubs.c str.cma -o $@ $(LIBCMO) $(CMO) 90 | 91 | $(NAME).opt: $(CMX) $(LIBCMX) custom_unix_stubs.c 92 | $(OCAMLOPT) $(OCAMLOPT_FLAGS) custom_unix_stubs.c str.cmxa -o $@ $(LIBCMX) $(CMX) 93 | 94 | # -- debugging support 95 | 96 | load.ml: $(CMO) 97 | echo $(CMO) | tr ' ' \\012 | sed 's/^.*$$/#load "&";;/' > $@ 98 | 99 | # -- rules 100 | 101 | %.ml: %.nw 102 | $(NOTANGLE) '-L# %L "%F"%N' -R$@ $< > $@ 103 | 104 | %.mli: %.nw 105 | $(NOTANGLE) '-L# %L "%F"%N' -R$@ $< > $@ 106 | 107 | %.sig: %.ml 108 | $(OCAMLC) $(OCAMLC_FLAGS) -c -i $< > $@ 109 | 110 | %.cmo: %.ml 111 | $(OCAMLC) $(OCAMLC_FLAGS) -c $< 112 | 113 | %.cmx: %.ml 114 | $(OCAMLOPT) $(OCAMLOPT_FLAGS) -c $< 115 | 116 | %.cmi: %.mli 117 | $(OCAMLC) $(OCAMLC_FLAGS) -c $< 118 | 119 | %.mli %.ml: %.mly 120 | $(OCAMLYACC) $< 121 | 122 | %.ml: %.mll 123 | $(OCAMLLEX) $< 124 | 125 | # -- manual page 126 | 127 | %.1: %.pod 128 | $(POD2MAN) $< > $@ 129 | 130 | 131 | # -- special rules 132 | 133 | scanner.mli: syntax.nw 134 | $(NOTANGLE) '-L# %L "%F"%N' -R$@ $< > $@ 135 | 136 | scanner.mll: syntax.nw 137 | $(NOTANGLE) '-L# %L "%F"%N' -R$@ $< > $@ 138 | 139 | parser.mly: syntax.nw 140 | $(NOTANGLE) '-L# %L "%F"%N' -R$@ $< > $@ 141 | 142 | # -- documentation 143 | 144 | RERUN = Rerun (LaTeX|to get cross-references right) 145 | 146 | pdf: $(PDF) 147 | 148 | %.pdf: %.tex 149 | $(PDFLATEX) $< 150 | if egrep -s '$(RERUN)' $*.log ;then $(PDFLATEX) $<; fi 151 | if egrep -s '$(RERUN)' $*.log ;then $(PDFLATEX) $<; fi 152 | 153 | %.inc: %.nw 154 | $(NOWEAVE) -delay $< > $@ 155 | 156 | %.tex: %.inc 157 | echo " \documentclass[11pt]{article} " > $@ 158 | echo " \usepackage{a4wide} " >> $@ 159 | echo " \usepackage{hyperref} " >> $@ 160 | echo " \usepackage{noweb} " >> $@ 161 | echo " \noweboptions{breakcode} " >> $@ 162 | echo " \begin{document} " >> $@ 163 | echo " \input{$<} " >> $@ 164 | echo " \end{document} " >> $@ 165 | 166 | # -- configure 167 | 168 | $(TOP)/config.mk: 169 | echo "Have you run ./configure?" 170 | exit 1 171 | 172 | # -- dependencies 173 | 174 | DEPEND: $(ML) $(MLI) 175 | $(OCAMLDEP) $(INCLUDES) $(ML) $(MLI) > DEPEND 176 | 177 | include DEPEND 178 | -------------------------------------------------------------------------------- /src/annot.nw: -------------------------------------------------------------------------------- 1 | 2 | Module [[Position]] represents a position in a file as a pair of line 3 | number and column. An alternative representation would be the byte 4 | offset from the beginning of a file. 5 | 6 | 7 | <>= 8 | module Position: sig 9 | type line = int 10 | type column = int 11 | type t = line * column 12 | 13 | val to_string: t -> string 14 | val compare: t -> t -> int 15 | end 16 | @ 17 | 18 | Module [[ByPosition]] associates position intervals with data. We use 19 | this to map intervals to (string) annotations as denoted by type [[t]] 20 | below. 21 | 22 | <>= 23 | module ByPosition: Intervalmap.S 24 | with type point = Position.t 25 | and type interval = Position.t * Position.t 26 | 27 | type t = string ByPosition.t 28 | @ 29 | 30 | % ------------------------------------------------------------------ 31 | \subsection{Implementation} 32 | % ------------------------------------------------------------------ 33 | 34 | <>= 35 | module Position = struct 36 | type line = int 37 | type column = int 38 | type t = line * column 39 | 40 | let to_string (line,col) = Printf.sprintf "%04d:%04d" line col 41 | 42 | let compare (l,c) (l',c') = 43 | if l < l' then -1 44 | else if l > l' then 1 45 | else if c < c' then -1 46 | else if c > c' then 1 47 | else 0 48 | end 49 | 50 | module ByPosition = Intervalmap.Make(Position) 51 | 52 | type t = string ByPosition.t 53 | @ 54 | -------------------------------------------------------------------------------- /src/annot.pod: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | =head1 NAME 6 | 7 | annot -- lookup OCaml annotations 8 | 9 | =head1 SYNOPSIS 10 | 11 | B [B<-n>] [B<-r>] B<-type> I I [I] 12 | 13 | B B<-dump> [I] 14 | 15 | B B<-help> 16 | 17 | B B<-version> 18 | 19 | =head1 DESCRIPTION 20 | 21 | B looks up annotations for identifiers in an OCaml source file. 22 | Such annotations are produced by ocaml(1) and stored in *.annot files, 23 | which is either read from standard input, or from I. 24 | Currently, only the lookup of type annotations is supported. For lookup 25 | of an annotation, the identifier's source code position as I and 26 | I is passed to B. It is typically invoked from an editor 27 | to lookup the type of an identifier under the cursor. 28 | 29 | =head1 OPTIONS 30 | 31 | =over 4 32 | 33 | =item B<-type> I I 34 | 35 | Lookup the type annotation for position I and I and emit 36 | it to standard output. 37 | 38 | =item B<-dump> 39 | 40 | Dump the annotations read to stdout. This is mostly used for debugging. 41 | 42 | =item B<-n> 43 | 44 | Don't print a newline after emitting an annotation to standard output. 45 | This is sometimes useful when showing the output inside an editor. 46 | 47 | =item B<-r> 48 | 49 | When emitting an annotation include the source-code range for which the 50 | annotation is intended. This could be used to highlight it in an editor. 51 | 52 | 53 | =item B<-h> 54 | 55 | Emit short help message to stdout and exit with code 0. 56 | 57 | =item B<-version> 58 | 59 | Emit the version of B to stdout and exit with code 0. 60 | 61 | =back 62 | 63 | =head1 AUTHOR 64 | 65 | Please send feedback, bug reports, and experience reports to the author: 66 | Christian Lindig 67 | L 68 | 69 | =head1 COPYRIGHT 70 | 71 | Copyright (c) 2006 Christian Lindig . All 72 | rights reserved. 73 | 74 | Redistribution and use in source and binary forms, with or without 75 | modification, are permitted provided that the following conditions 76 | are met: 77 | 78 | =over 79 | 80 | =item 1. 81 | 82 | Redistributions of source code must retain the above copyright 83 | notice, this list of conditions and the following disclaimer. 84 | 85 | =item 2. 86 | 87 | Redistributions in binary form must reproduce the above 88 | copyright notice, this list of conditions and the following 89 | disclaimer in the documentation and/or other materials provided 90 | with the distribution. 91 | 92 | =item 3. 93 | 94 | The names of the contributors may not be used to endorse or promote 95 | products derived from this software without specific prior written 96 | permission. 97 | 98 | 99 | =back 100 | 101 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 102 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 103 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 104 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 105 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 106 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 107 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 108 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 109 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 110 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 111 | POSSIBILITY OF SUCH DAMAGE. 112 | 113 | 114 | =head1 VERSION 115 | 116 | $Id$ 117 | 118 | =head1 SEE ALSO 119 | 120 | ocaml(1), ocamlc(1) 121 | -------------------------------------------------------------------------------- /src/custom_unix_stubs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Anil Madhavapeddy 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | CAMLprim value unix_realpath(value path) 29 | { 30 | char buffer[PATH_MAX]; 31 | char *r; 32 | r = realpath(String_val(path), buffer); 33 | if (r == NULL) caml_failwith("realpath"); 34 | return copy_string(buffer); 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/intervalmap.nw: -------------------------------------------------------------------------------- 1 | 2 | \section{Interval Map} 3 | 4 | An interval map associates intervals with data. An interval $[l,h] = \{ 5 | p \mid l \le p \le h \}$ is delimited by two \emph{points} $l$ and high 6 | $h$ and is associated with a datum. A lookup operation finds the 7 | smallest interval enclosing a point $p$ and returns the datum associated 8 | with that interval. Intervals may nest: two intervals must be either 9 | disjoint or one must contain the other. 10 | 11 | Interval maps are parameterized over points: a point is a comparable 12 | type, and an interval is delimited by two points. 13 | 14 | <>= 15 | module type POINT = sig 16 | type t 17 | val compare: t -> t -> int 18 | val to_string: t -> string 19 | end 20 | @ 21 | 22 | An interval map is polymorphic in the data associated with an interval. 23 | Function [[add]] builds a map, function [[find]] find the smallest 24 | interval enclosing a point and returns interval and the associated 25 | value, or [[None]]. 26 | 27 | Currently, there is no function to remove intervals from a map. 28 | 29 | <>= 30 | module type S = sig 31 | type 'a t 32 | type point 33 | type interval 34 | 35 | val empty: 'a t 36 | val add: interval -> 'a -> 'a t -> 'a t 37 | val find: 'a t -> point -> (interval * 'a) option 38 | val dump: ('a -> string) -> 'a t -> unit 39 | end 40 | 41 | <>= 42 | <> 43 | module Make (P: POINT): S 44 | with type point = P.t 45 | and type interval = P.t * P.t 46 | @ 47 | 48 | \subsection{Implementation} 49 | 50 | The implementation is simple but inefficient: a map is a list of 51 | intervals with associated data. The [[find]] operation searches the list 52 | linearly. More efficient representations exist but don't pat off unless 53 | we search the same map frequently. 54 | 55 | <>= 56 | <> 57 | 58 | module Make (P: POINT) = struct 59 | <> 60 | end 61 | 62 | 63 | <>= 64 | type point = P.t 65 | type interval = P.t * P.t 66 | 67 | type 'a t = (interval * 'a) list 68 | 69 | let empty = [] 70 | let add interval x t = (interval,x) :: t 71 | @ 72 | 73 | Operation [[<*]] compares points, whereas operation [[<:]] compares 74 | intervals. An interval $[l,h]$ is smaller than another one, if both its 75 | points are inside the other one. 76 | 77 | <>= 78 | let (<*) px py = P.compare px py < 0 79 | let (>*) px py = P.compare px py > 0 80 | let (<=*) px py = P.compare px py <= 0 81 | let lo (l,h) = l 82 | let hi (l,h) = h 83 | 84 | let inside point (lo,hi) = lo <=* point && point <=* hi 85 | let (<:) r1 r2 = inside (lo r1) r2 && inside (hi r1) r2 86 | @ 87 | 88 | The [[find]] operation searches for an interval that encloses a given 89 | point. Once this is found, [[find]] searches to find a smaller interval 90 | that also includes the point. 91 | 92 | <>= 93 | let find intervals point = 94 | let rec loop result intervals = match result, intervals with 95 | | None, [] -> None 96 | | Some(r,x),[] -> Some (r,x) 97 | | None, (r,x)::rs when inside point r -> loop (Some (r,x)) rs 98 | | Some (r',x'), (r,x)::rs 99 | when inside point r && r <: r' -> loop (Some (r,x)) rs 100 | | _, r::rs -> loop result rs 101 | in 102 | loop None intervals 103 | @ 104 | 105 | The [[dump]] operation is mostly used for debugging. 106 | 107 | <>= 108 | let dump to_string intervals = 109 | let interval ((left,right),annot) = Printf.printf "[%s,%s] %s\n" 110 | (P.to_string left) (P.to_string right) (to_string annot) 111 | in 112 | List.iter interval intervals 113 | -------------------------------------------------------------------------------- /src/main.nw: -------------------------------------------------------------------------------- 1 | 2 | 3 | \section{Main Module} 4 | 5 | Everything starts at the main module: it parses the command line and 6 | calls functions that do the actual work. Catching and reporting errors 7 | to the user is also the main modules' responsibility. The Main module 8 | has an interface but since [[main]] is called automatically it is not of 9 | much use. 10 | 11 | <>= 12 | val main: unit -> unit (* executed automatically *) 13 | @ 14 | 15 | \subsection{Implementation} 16 | 17 | <>= 18 | exception Usage of string 19 | 20 | let error fmt = Printf.kprintf (fun msg -> raise (Usage msg)) fmt 21 | let printf = Printf.printf 22 | let sprintf = Printf.sprintf 23 | let version = "1.1.0" 24 | let (@@) f x = f x 25 | 26 | let this = Sys.argv.(0) 27 | 28 | let synopsis = 29 | [ "usage: "^this^" [options] [file.annot]" 30 | ; "" 31 | ; "-type line column lookup type annotation for position" 32 | ; "-dump dump annotations to stdout" 33 | ; "-n suppress newline after last line" 34 | ; "-r include range for annotation" 35 | ; "-version identify version of "^this^" on stdout" 36 | ; "-h, -help offer minimal help on stdout" 37 | ; "" 38 | ; "Maintainer: Anil Madhavapedddy http://anil.recoil.org/" 39 | ; "Author: Christian Lindig http://www.st.cs.uni-sb.de/~lindig/" 40 | ] 41 | 42 | let usage msg = List.iter prerr_endline (msg :: synopsis) 43 | let help () = List.iter print_endline synopsis 44 | 45 | let version () = 46 | List.iter print_endline 47 | [ this^" version " ^ version ] 48 | @ 49 | 50 | <>= 51 | module A = Annot.ByPosition 52 | @ 53 | 54 | Operation [[with_file]] opens a file for reading and passes the file 55 | handle to an action. It takes care of closing the file, even in the case 56 | of errors. 57 | 58 | <>= 59 | 60 | external realpath: string -> string = "unix_realpath" 61 | 62 | let find_file file = 63 | let cwd = realpath (Sys.getcwd ()) in 64 | let filedir = realpath (Filename.dirname file) in 65 | let filename = Filename.basename file in 66 | let file = Filename.concat filedir filename in 67 | (* Find common prefix between requested file and cwd *) 68 | match Str.bounded_split (Str.regexp_string cwd) file 1 with 69 | |[suffix] -> 70 | let result = ref None in 71 | (* Look in cwd+backend+suffix in descending priority *) 72 | let backends = [ ""; "_build"; "_build/unix-socket"; 73 | "_build/unix-direct"; "_build/node"; "_build/xen" ] in 74 | List.iter (fun backend -> 75 | let f = Printf.sprintf "%s/%s/%s" cwd backend suffix in 76 | if Sys.file_exists f then 77 | result := Some f; 78 | ) (List.rev backends); 79 | (match !result with 80 | | Some r -> r 81 | | None -> raise Not_found) 82 | |_ -> raise Not_found 83 | 84 | let with_file file action = 85 | let file = 86 | try find_file file 87 | with Not_found -> file in (* being clever didn't work, try being stupid *) 88 | let ic = open_in file in 89 | try 90 | let result = action ic in 91 | close_in ic; result 92 | with 93 | x -> close_in ic; raise x 94 | 95 | let annotations_from channel = 96 | let lexbuf = Lexing.from_channel channel in 97 | try 98 | Parser.annotations Scanner.token lexbuf 99 | with 100 | Parsing.Parse_error -> 101 | error "parse error at offset %d" (Lexing.lexeme_start lexbuf) 102 | 103 | let int x = try int_of_string x with Failure _ -> error "not an int: %s" x 104 | @ 105 | 106 | Options that control the format when emitting annotations. 107 | 108 | <>= 109 | type options = 110 | { mutable newline: bool (* emit newline after annotation *) 111 | ; mutable range: bool (* include range in type annotation *) 112 | } 113 | 114 | let options = 115 | { newline = true 116 | ; range = false 117 | } 118 | @ 119 | 120 | Read type annotation from channel [[ic]] and report the annotation for a 121 | given [[line]] and [[column]] to stdout. We first build an interval map 122 | and then search it for the given position. 123 | 124 | <>= 125 | let typeannot options line column ic = 126 | let line, column = int line, int column in 127 | let nl = if options.newline then "\n" else "" in 128 | let range ((l,c),(l',c')) = match options.range with 129 | | false -> "" 130 | | true -> Printf.sprintf "%d:%d-%d:%d " l c l' c' in 131 | let annots = annotations_from ic in 132 | match A.find annots (line, column) with 133 | | Some (r,s) -> print_string (range r ^ s ^ nl) 134 | | None -> print_string ("no type found" ^ nl) 135 | @ 136 | 137 | Read annotations from channel [[ic]], build an interval map from it and 138 | dump it to stdout (for debugging). 139 | 140 | <>= 141 | let dump ic = A.dump (fun x -> x) @@ annotations_from ic 142 | 143 | let main () = 144 | let argv = Array.to_list Sys.argv in 145 | let args = List.tl argv in 146 | let rec parse = function 147 | | "-h" :: [] -> help () 148 | | "-help" :: [] -> help () 149 | | "-version" :: [] -> version () 150 | | "-n" :: args -> options.newline <- false; parse args 151 | | "-r" :: args -> options.range <- true; parse args 152 | | ["-type"; line;col;file] -> with_file file 153 | @@ typeannot options line col 154 | | ["-type"; line;col] -> typeannot options line col stdin 155 | | ["-dump"; file] -> with_file file @@ dump 156 | | ["-dump"] -> dump stdin 157 | | x :: _ -> usage @@ sprintf "error: illegal command line argument %s" x 158 | | [] -> usage "error: expected an option" 159 | in 160 | try parse args with 161 | | Usage msg -> (usage msg; exit 1) 162 | | Sys_error msg -> (print_endline msg; exit 1) 163 | 164 | let () = if !Sys.interactive then () else main () 165 | @ 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/position.nw: -------------------------------------------------------------------------------- 1 | 2 | \section{Position} 3 | 4 | Module [[Position]] represents a position in a file as a pair of line 5 | number and column. An alternative representation would be the byte 6 | offset from the beginning of a file. 7 | 8 | <>= 9 | type line = int 10 | type column = int 11 | type t = line * column 12 | 13 | val to_string: t -> string 14 | val compare: t -> t -> int 15 | 16 | <>= 17 | type line = int 18 | type column = int 19 | type t = line * column 20 | 21 | let to_string (line,col) = Printf.sprintf "%04d:%04d" line col 22 | 23 | let compare (l,c) (l',c') = 24 | if l < l' then -1 25 | else if l > l' then 1 26 | else if c < c' then -1 27 | else if c > c' then 1 28 | else 0 29 | @ 30 | -------------------------------------------------------------------------------- /src/syntax.nw: -------------------------------------------------------------------------------- 1 | 2 | \section{Scanner and Parser for Annotations} 3 | 4 | % ------------------------------------------------------------------ 5 | \subsection{Parser} 6 | % ------------------------------------------------------------------ 7 | 8 | <>= 9 | %{ 10 | module A = Annot.ByPosition 11 | 12 | exception Error of string 13 | let error fmt = Printf.kprintf (fun msg -> raise (Error msg)) fmt 14 | let (@@) f x = f x 15 | %} 16 | <> 17 | %% 18 | <> 19 | @ 20 | 21 | <>= 22 | %start annotations 23 | %type annotations 24 | 25 | %token EOF 26 | %token STRING 27 | %token INT 28 | %token ANNOT IDENT CALL 29 | @ 30 | 31 | <>= 32 | annotations : annotations annot { let interval, annots = $2 in 33 | List.fold_right (A.add interval) annots $1 34 | } 35 | | /**/ { A.empty } 36 | 37 | annot : location location data { ($1,$2), $3 } 38 | 39 | data : ANNOT data { $1 :: $2 } 40 | | ANNOT { [$1] } 41 | | IDENT data { $2 } 42 | | IDENT { [] } 43 | | CALL data { $2 } 44 | | CALL { [] } 45 | ; 46 | 47 | location : STRING INT INT INT { $2, $4-$3 } 48 | 49 | @ 50 | 51 | % ------------------------------------------------------------------ 52 | \subsection{Scanner} 53 | % ------------------------------------------------------------------ 54 | 55 | The scanner splits the input into tokens. Tokens are defined by the 56 | parser (in [[parser.mli]]). 57 | 58 | <>= 59 | val token: Lexing.lexbuf -> Parser.token 60 | 61 | <>= 62 | { 63 | 64 | module P = Parser (* tokens are defined here *) 65 | 66 | let get = Lexing.lexeme 67 | let getchar = Lexing.lexeme_char 68 | let strlen = String.length 69 | let pos_start = Lexing.lexeme_start 70 | let pos_end = Lexing.lexeme_end 71 | 72 | exception Error of string 73 | let error fmt = Printf.kprintf (fun msg -> raise (Error msg)) fmt 74 | let (@@) f x = f x 75 | 76 | } 77 | @ 78 | 79 | <>= 80 | let digit = ['0'-'9'] 81 | let ws = [' ' '\t' '\r'] 82 | let nl = '\n' 83 | @ 84 | 85 | <>= 86 | rule token = parse 87 | eof { P.EOF } 88 | | ws+ { token lexbuf } 89 | | nl { token lexbuf } 90 | 91 | | digit+ { P.INT(int_of_string (get lexbuf)) } 92 | | '"' ([^'"']+ as str) '"'{ P.STRING (str)} 93 | | "type(\n" { annot `Type (Buffer.create 80) lexbuf } 94 | | "ident(\n" { annot `Ident (Buffer.create 80) lexbuf } 95 | | "call(\n" { annot `Call (Buffer.create 80) lexbuf } 96 | | _ { let str = String.escaped @@ get lexbuf in 97 | let off = Lexing.lexeme_start lexbuf in 98 | error "illegal character: '%s' at offset %d" str off 99 | } 100 | 101 | and annot mode buffer = parse 102 | eof { error "unexpected end of file" } 103 | | [^ '\n']+ { Buffer.add_string buffer (get lexbuf) 104 | ; annot mode buffer lexbuf 105 | } 106 | | nl { Buffer.add_string buffer (get lexbuf) 107 | ; annot mode buffer lexbuf 108 | } 109 | | nl ')' { match mode with 110 | |`Type -> P.ANNOT(Buffer.contents buffer) 111 | |`Ident -> P.IDENT(Buffer.contents buffer) 112 | |`Call -> P.CALL(Buffer.contents buffer) 113 | } 114 | | _ { let str = String.escaped @@ get lexbuf in 115 | let off = Lexing.lexeme_start lexbuf in 116 | error "illegal character: '%s' at offset %d" str off 117 | } 118 | @ 119 | -------------------------------------------------------------------------------- /src/test.in: -------------------------------------------------------------------------------- 1 | 4 6 2 | 4 8 3 | 5 9 4 | 5 13 5 | 7 16 6 | 7 23 7 | -------------------------------------------------------------------------------- /src/test.ref: -------------------------------------------------------------------------------- 1 | 'a -> 'a 2 | 'a 3 | ('a -> 'b) -> 'a list -> 'b list 4 | 'a -> 'b 5 | 'a -> 'b 6 | ('a -> 'b) -> 'a list -> 'b list 7 | -------------------------------------------------------------------------------- /tools/nofake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # $Id: nofake,v 1.1 2004/05/24 14:51:48 lindig Exp $ 4 | # 5 | # Generated from nofake.nw. Do not edit! Edit nofake.nw instead and 6 | # run nofake on it: 7 | # ./nofake -Rnofake > nofake.pl && cp nofake.pl nofake 8 | # 9 | # The manual page is at the end of this file in Perl's 10 | # POD format. You can format it using pod2man(1): 11 | # 12 | # pod2man nofake > nofake.1 13 | # nroff -man nofake.1 | more 14 | # 15 | # This software is in the public domain; for details, see the manual 16 | # page. 17 | 18 | use warnings; 19 | 20 | sub version { 21 | print <<'EOF'; 22 | $Id: nofake,v 1.1 2004/05/24 14:51:48 lindig Exp $ 23 | (c) 2002, 2003 Christian Lindig 24 | 25 | NoFake is derived from the public domain NegWeb 1.0.1 26 | http://boswa.com/misc/negweb by Darrell Johnson . 27 | EOF 28 | } 29 | 30 | $lineformat='#line %L "%F"%N'; #default format for #line directive 31 | $sync=0; #default: do not emit #line directives 32 | 33 | $fname=''; #file name of noweb file 34 | $chunk='*'; #default chunk to extract 35 | %chunks=(); #hash table for chunks 36 | 37 | sub line_directive { 38 | my ($fname,$line)=@_; 39 | my $ret=$lineformat; 40 | $ret=~s/%F/$fname/g; 41 | $ret=~s/%L/$line/g; 42 | $ret=~s/%N/\n/g; 43 | return "\n$ret"; 44 | } 45 | sub read_file { 46 | my ($fname,$sync)=@_; 47 | local *INFILE; 48 | if($fname eq ''){ 49 | *INFILE=STDIN; 50 | $fname="Standard Input"; 51 | }else{ 52 | if(!open(INFILE, '<'.$fname)){ 53 | print STDERR "can't open file '$fname'\n"; 54 | exit(1); 55 | } 56 | } 57 | my $chunk=''; # name of actual chunk, '' iff outside of chunk 58 | my $line; 59 | for(my $i=1;$line=;$i++) { 60 | if($line =~ /^<<([^<>]+)>>=\s*$/o){ 61 | $chunk=$1; 62 | if(!exists $chunks{$chunk}){ 63 | $chunks{$chunk}=''; 64 | } 65 | if($sync){ 66 | $chunks{$chunk}.=line_directive($fname,$i+1); 67 | } 68 | } elsif($chunk ne '') { 69 | #inside <>= ... @ chunk 70 | if($line=~/^(@|<<[^<>]+>>=\s*)/o){ 71 | # reached end of chunk 72 | $chunk=''; 73 | }else{ 74 | # regular line inside chunk 75 | $chunks{$chunk}.=$line; 76 | } 77 | } # else outside chunk - nothing to do 78 | } 79 | } 80 | %cache=(); 81 | %being_extracted=(); 82 | sub extract { 83 | my ($chunk)=@_; 84 | 85 | if(exists $being_extracted{$chunk} && $being_extracted{$chunk} > 0){ 86 | print STDERR 87 | "Code chunk <<$chunk>> is used in its own definition.\n"; 88 | exit(1); 89 | } 90 | if(exists $cache{$chunk}){ 91 | return $cache{$chunk}; 92 | } 93 | if(!exists $chunks{$chunk}){ 94 | print STDERR 95 | "Code chunk <<$chunk>> is requested, but doesn't exist.\n"; 96 | exit(1); 97 | } 98 | $being_extracted{$chunk}++; 99 | $cache{$chunk}=''; 100 | # obtain chunk and process it recursively 101 | foreach my $line (split /\n/, $chunks{$chunk}){ 102 | if($line =~/^(.*[^\@]*)<<([^<>@]+)>>(.*)$/o){ 103 | my $prefix=$1; 104 | my $postfix=$3; 105 | my $next=$2; 106 | my $indent = length($prefix); 107 | my $i = 0; 108 | 109 | # first line from $next is prefixed with $prefix 110 | # following lines are indented by length($prefix) 111 | # $i denotes line counter for lines from $next 112 | $cache{$chunk} .= $prefix; 113 | $cache{$chunk} .= 114 | join("\n", map {$i++; ($i > 1 ? " " x $indent : "") . $_} 115 | split(/\n/,extract($next))); 116 | $cache{$chunk} .= "$postfix\n"; 117 | 118 | }else{ 119 | $line =~ s/\@(<<|>>)/$1/og; 120 | $cache{$chunk}.="$line\n"; 121 | } 122 | } 123 | $being_extracted{$chunk}--; 124 | return $cache{$chunk}; 125 | } 126 | 127 | sub usage { 128 | my $arg = shift @_; 129 | 130 | print STDERR < [B<-R>I] [B<-L>[I]] [I] 160 | 161 | B [B<-version>|B<-v>] 162 | 163 | =head1 DESCRIPTION 164 | 165 | Noweb(1) is a literate-programming tool like Knuth's WEB. A noweb file 166 | contains program source code interleaved with documentation. Extracting 167 | the source code for compilation requires notangle(1). To allow source 168 | code to be shipped to users not using noweb(1), B offers the 169 | most commonly used functionality of notangle(1) as a simple perl(1) 170 | script. Alas, B extracts source code from a file in noweb(1) 171 | syntax: B reads I and extracts the code chunke named 172 | I to stdout. If no I is provided, B reads from 173 | stdin, if no I is named, B extracts the chunk C<*>. 174 | 175 | =head1 OPTIONS 176 | 177 | =over 4 178 | 179 | =item B<-R>I 180 | 181 | Extract chunk I (recursively) from the noweb file and write it to 182 | stdout. Unlike notangle, only one chunk can be extracted per invocation 183 | of B. 184 | 185 | =item B<-L>[I] 186 | 187 | B emits cpp(1)-style C<#line> directives to allow a compiler 188 | emit error messages that refer to I rather than the extracted 189 | source code directly. The optional I allows to provided the 190 | format of the line directive: C<-L'#line %L "%F"%N'>. In I C<%F> 191 | indicates the name of the source file, C<%L> the line number, and C<%N> 192 | a newline. The default C<#line %L "%F"%N> is suitable for C compilers. 193 | 194 | =back 195 | 196 | =head1 SYNTAX OF NOWEB FILES 197 | 198 | The authoritive source for the syntax of noweb files is the noweb(1) 199 | documentation. However, here is an example: 200 | 201 | <>= 202 | <> 203 | 204 | int main(int argc, char** argv) 205 | { 206 | <> 207 | return 0; 208 | } 209 | 210 | <>= 211 | printf("Hello World!\n"); 212 | @ 213 | 214 | <>= 215 | #include /* for printf */ 216 | @ 217 | 218 | A chunk is defined by CEchunkEE=> and reaches up to 219 | the next definition or a line starting with C<@>. A chunk can 220 | recursivley refer to other chunks: chunk C refers to 221 | C and C. A chunk is referred to by 222 | CEchunkEE>. To use the CE> and CE> 223 | character literally in a program, precede them with a C<@>. 224 | 225 | =head1 LIMITATIONS 226 | 227 | The B architecture is much simpler than that of notangle(1) and 228 | therefore many things do not work. In particular: 229 | 230 | =over 4 231 | 232 | =item * 233 | 234 | B accepts the B<-R>I option only once. 235 | 236 | =item * 237 | 238 | Line directives (C<#line>) are indented. In the case of cpp(1)-style 239 | line directives this is still ANSI/ISO C conforming but might cause 240 | problems with other directives and older versions of cpp(1). 241 | 242 | =item * 243 | 244 | Do not use C<@> in chunk names. 245 | 246 | =item * 247 | 248 | B does not accept the B<-filter> I option that 249 | B uses to filter chunks before they are emitted. 250 | 251 | =item * 252 | 253 | A noweb file must not refer to more than one chunk per line. The 254 | following code is I handled correctly by B because the 255 | chunk C<*> contains two chunk references in one line. 256 | 257 | <<*>>= 258 | <> <> 259 | 260 | <>= 261 | one 262 | @ 263 | 264 | 265 | =back 266 | 267 | =head1 COPYING 268 | 269 | This software is in the public domain. 270 | 271 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 272 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 273 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 274 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND COPYRIGHT HOLDER BE 275 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 276 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 277 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 278 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 279 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 280 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 281 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 282 | SUCH DAMAGE. 283 | 284 | =head1 AUTHOR 285 | 286 | Christian Lindig 287 | 288 | =head1 SEE ALSO 289 | 290 | noweb(1), notangle(1), perl(1), cpp(1) 291 | 292 | Norman Ramsey, Literate programming simplified, IEEE Software 293 | 11(5):97-105, September 1994. 294 | --------------------------------------------------------------------------------