ATS is known for its sophisticated type system, but it also has a template system that has been the focus of recent work. Rather than going into technical details or comparing templates to other forms of generic programming, I wanted to give an example.
Code Counting
Suppose we want to count lines of code, à la cloc. Briefly, this means detecting various different programming languages and then examining file contents to count the number of lines, comments, and blanks.
A naïve approach would be to define a type
vtypedef file = @{ lines = int, blanks = int, comments = int, doc_comments = int }
and functions
fn read_file(fp: string) : string
fn count_haskell(file_contents: string) : file
fn count_j(file_contents: string) : file
…
This is unnecessarily slow, particularly in a code-counting tool like cloc: it will allocate each time a file is opened. Instead, we can define a set of function templates for each programming language that count lines of code from a stream of characters, viz.
fn {a:vt@ype} advance_char$lang (char, &a >> _, &file >> _) : void
fn {a:vt@ype} free$lang (a) : void
fn {a:vt@ype} init$lang (&a? >> a) : void
The workhorse here is advance_char$lang
: it takes a character, a reference to
a value of type a
, and a reference to a value of type file
. For each programming
language we define a custom parse state a
, and implement advance_char$lang
for that particular a
.
As an example consider the implemenation of advance_char$lang
for
Egison in
SATS/lang/egison.sats
:
datavtype parse_state_egi =
| line_comment
| regular
| post_newline_whitespace
fn free_st_egi(parse_state_egi) : void
overload free with free_st_egi
and DATS/lang/egison.dats
:
staload "SATS/file.sats"
staload "SATS/lang/egison.sats"
staload "SATS/lang/common.sats"
implement free_st_egi (st) =
case+ st of
| ~line_comment() => ()
| ~regular() => ()
| ~post_newline_whitespace() => ()
implement free$lang<parse_state_egi> (st) =
free_st_egi(st)
implement init$lang<parse_state_egi> (st) =
st := post_newline_whitespace
implement advance_char$lang<parse_state_egi> (c, st, file_st) =
case+ st of
| regular() =>
begin
case+ c of
| '\n' => (free(st) ; file_st.lines := file_st.lines + 1 ; st := post_newline_whitespace)
| _ => ()
end
| line_comment() =>
begin
case+ c of
| '\n' => (free(st) ; file_st.comments := file_st.comments + 1 ; st := post_newline_whitespace)
| _ => ()
end
| post_newline_whitespace() =>
begin
case+ c of
| '\n' => (file_st.blanks := file_st.blanks + 1)
| '\t' => ()
| ' ' => ()
| ';' => (free(st) ; st := line_comment)
| _ => (free(st) ; st := regular)
end
Tying this all together, we define a template function to count lines of code in a file:
#define BUFSZ 32768
staload "libats/libc/SATS/stdio.sats"
staload "SATS/file.sats"
staload "SATS/lang/common.sats"
fn {a:vt@ype} count_for_loop { l : addr | l != null }{m:nat}{ n : nat | n <= m }( pf : !bytes_v(l, m) | p : ptr(l)
, parse_st : &a >> _
, bufsz : size_t(n)
) : file =
let
var res: file = empty_file
var i: size_t
val () = for* { i : nat | i <= n } .<n-i>. (i : size_t(i)) =>
(i := i2sz(0) ; i < bufsz ; i := i + 1)
let
var current_char = byteview_read_as_char(pf | add_ptr_bsz(p, i))
in
advance_char$lang<a>(current_char, parse_st, res)
end
in
res
end
fn {a:vt@ype} count_file_buf { l : addr | l != null }(pf : !bytes_v(l, BUFSZ) | p : ptr(l), inp : !FILEptr1) : file =
let
var init_st: a
val () = init$lang<a>(init_st)
fun loop { l : addr | l != null }(pf : !bytes_v(l, BUFSZ) | inp : !FILEptr1, st : &a >> _, p : ptr(l)) : file =
let
var file_bytes = freadc(pf | inp, i2sz(BUFSZ), p)
extern
praxi lt_bufsz {m:nat} (size_t(m)) : [m <= BUFSZ] void
in
if file_bytes = 0 then
empty_file
else
let
prval () = lt_bufsz(file_bytes)
var acc = count_for_loop<a>(pf | p, st, file_bytes)
in
acc + loop(pf | inp, st, p)
end
end
var ret = loop(pf | inp, init_st, p)
val () = free$lang<a>(init_st)
in
ret
end
Notice that we can define the count_file_buf
template in terms of
advance_char$lang<a>
. That is, we provide a generic way to stream characters
from a file, and then call count_file_bug<parse_state_egi>
(for instance) when
we want to count lines of code in an Egison file.
The full setup is available here.
Note
The error messages related to misusing templates are not particularly refined.
If, for example, we attempt to call count_file_buf<parse_state_egi>
without
implementing advance_char$lang<parse_state_egi>
, we get
.atspkg/c/src/wc-demo.c:76635:28: error: ‘PMVtmpltcstmat’ undeclared (first use in this function)
76635 | ATSINSmove_void(tmp937__6, PMVtmpltcstmat[0](init$lang<S2Ecst(parse_state_egi)>)(ATSPMVrefarg1(ATSPMVptrof(tmpref936__6)))) ;
| ^~~~~~~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76635:28: note: each undeclared identifier is reported only once for each function it appears in
76635 | ATSINSmove_void(tmp937__6, PMVtmpltcstmat[0](init$lang<S2Ecst(parse_state_egi)>)(ATSPMVrefarg1(ATSPMVptrof(tmpref936__6)))) ;
| ^~~~~~~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76635:46: error: ‘init$lang’ undeclared (first use in this function)
76635 | ATSINSmove_void(tmp937__6, PMVtmpltcstmat[0](init$lang<S2Ecst(parse_state_egi)>)(ATSPMVrefarg1(ATSPMVptrof(tmpref936__6)))) ;
| ^~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76635:56: warning: implicit declaration of function ‘S2Ecst’ [-Wimplicit-function-declaration]
76635 | ATSINSmove_void(tmp937__6, PMVtmpltcstmat[0](init$lang<S2Ecst(parse_state_egi)>)(ATSPMVrefarg1(ATSPMVptrof(tmpref936__6)))) ;
| ^~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76635:63: error: ‘parse_state_egi’ undeclared (first use in this function)
76635 | ATSINSmove_void(tmp937__6, PMVtmpltcstmat[0](init$lang<S2Ecst(parse_state_egi)>)(ATSPMVrefarg1(ATSPMVptrof(tmpref936__6)))) ;
| ^~~~~~~~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76635:80: error: expected expression before ‘)’ token
76635 | ATSINSmove_void(tmp937__6, PMVtmpltcstmat[0](init$lang<S2Ecst(parse_state_egi)>)(ATSPMVrefarg1(ATSPMVptrof(tmpref936__6)))) ;
| ^
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76651:46: error: ‘free$lang’ undeclared (first use in this function)
76651 | ATSINSmove_void(tmp946__6, PMVtmpltcstmat[0](free$lang<S2Ecst(parse_state_egi)>)(tmpref936__6)) ;
| ^~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76651:80: error: expected expression before ‘)’ token
76651 | ATSINSmove_void(tmp946__6, PMVtmpltcstmat[0](free$lang<S2Ecst(parse_state_egi)>)(tmpref936__6)) ;
| ^
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c: In function ‘count_for_loop_201__201__6’:
.atspkg/c/src/wc-demo.c:76944:28: error: ‘PMVtmpltcstmat’ undeclared (first use in this function)
76944 | ATSINSmove_void(tmp934__6, PMVtmpltcstmat[0](advance_char$lang<S2Ecst(parse_state_egi)>)(tmpref932__6, ATSPMVrefarg1(arg1), ATSPMVrefarg1(ATSPMVptrof(tmpref927__6)))) ;
| ^~~~~~~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76944:46: error: ‘advance_char$lang’ undeclared (first use in this function)
76944 | ATSINSmove_void(tmp934__6, PMVtmpltcstmat[0](advance_char$lang<S2Ecst(parse_state_egi)>)(tmpref932__6, ATSPMVrefarg1(arg1), ATSPMVrefarg1(ATSPMVptrof(tmpref927__6)))) ;
| ^~~~~~~~~~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76944:71: error: ‘parse_state_egi’ undeclared (first use in this function)
76944 | ATSINSmove_void(tmp934__6, PMVtmpltcstmat[0](advance_char$lang<S2Ecst(parse_state_egi)>)(tmpref932__6, ATSPMVrefarg1(arg1), ATSPMVrefarg1(ATSPMVptrof(tmpref927__6)))) ;
| ^~~~~~~~~~~~~~~
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
.atspkg/c/src/wc-demo.c:76944:88: error: expected expression before ‘)’ token
76944 | ATSINSmove_void(tmp934__6, PMVtmpltcstmat[0](advance_char$lang<S2Ecst(parse_state_egi)>)(tmpref932__6, ATSPMVrefarg1(arg1), ATSPMVrefarg1(ATSPMVptrof(tmpref927__6)))) ;
| ^
/home/vanessa/.atspkg/0.3.13/lib/ats2-postiats-0.3.13/ccomp/runtime/pats_ccomp_instrset.h:284:39: note: in definition of macro ‘ATSINSmove_void’
284 | #define ATSINSmove_void(tmp, command) command
| ^~~~~~~
)