Building a
Bullshit Language

#bullshitlanguage

AnnotationScript

Java annotations with a LISP

Demo time!

Jan Ouwens

 

EqualsVerifierjqno.nl jqno

#bullshitlanguage

WHY!?

WHY!?

WHY!?

@Autowired
@Bean
@Column(name = "wat")
@Deprecated
@JsonAlias("whynot")
@PostMapping("/endpoint/wtf")
@Test
public void waitwhat() {}

WHY!?

AnnotationScript

is

a LISP

LISP

What’s a LISP?

  • Common Lisp
  • Scheme
  • Emacs
  • Racket
  • Clojure

Syntax

(define (fizzbuzz x y)
  (cond ((eq? (remainder x 15) 0) (display "FizzBuzz\n"))
        ((eq? (remainder x 3) 0) (display "Fizz\n"))
        ((eq? (remainder x 5) 0) (display "Buzz\n"))
        (else (display x) (display "\n")))

  (cond ((< x y) (fizzbuzz (+ x 1) y))
        (else ())))

(fizzbuzz 1 100)

Syntax

(+ x 10)

The Power of LISP

(+ x 10)

((if (#t) + -) x 10)

Map to AnnotationScript

(if (< x 0) "a" "b")








Map to AnnotationScript

(
  if
  (
    <
    x
    0)
  "a"
  "b"
)

Map to AnnotationScript

(
       if
           (
          <
          x
          0  )
       "a"
       "b"
)

Map to AnnotationScript


@Zero("if")
@Zero(list={
    @One("<"),
    @One("x"),
    @One("0")})
@Zero("'a'")
@Zero("'b'")
public static class First {}

Implementing LISP

 

Use Peter Norvig’s blog post

Implementation

Architecture

Architecture

Tokenizer

Tokenizer

@Zero("if")
@Zero(list={
    @One("<"),
    @One("x"),
    @One("0")
})
@Zero("'a'")
@Zero("'b'")

Tokenizer

@Zero("if")       // 'if'
@Zero(list={      // '('
    @One("<"),    // '<'
    @One("x"),    // 'x'
    @One("0")     // '0'
})                // ')'
@Zero("'a'")      // 'a'
@Zero("'b'")      // 'b'

DONE! 🥳

But …

why so @Weird?

Annotations, first try

@Parenthesis("if", @Parenthesis("<", "x", "0"), "'a'", "'b'")

Annotations, first try

No nesting annotations!

Annotations, second try

@Open
@Symbol("if")
@Open@Symbol("<")@Symbol("x")@Symbol("0")@Close
@Symbol("'a'")
@Symbol("'b'")
@Close

Annotations, second try

Sure! I’ll group them for you

 

 

Open[] opens = { @Open, @Open };
Symbol[] symbols = { @Symbol("if"), @Symbol("x"), @Symbol("0"),
                     @Symbol("'a'"), @Symbol("'b'") };
Close[] closes = { @Close, @Close };

Annotations, third try

public @interface Syntax {}  // 👈🏻 smart-ass!

public @interface Open extends Syntax {}
public @interface Symbol extends Syntax {}
public @interface Close extends Syntax {}

Syntax[] code = { @Open, @Symbol("if"), @Open, @Symbol("<"),
                  @Symbol("x"), @Symbol("0"), @Close, @Symbol("'a'"),
                  @Symbol("'b'"), @Close };

Annotations, third try

No extending annotations!

Annotations, fourth and final try

@Zero("if")
@Zero(list={@One("<"), @One("x"), @One("0")})
@Zero("'a'")
@Zero("'b'")

Annotations, fourth and final try

I see no issue with that!

Make as many as you like!

🥱

Architecture

Parser

Parser

  • Atoms (0, 'a') remain the same
  • Symbols (define, if, <) are wrapped in Symbol class
  • ( starts a sub-list
  • ) ends a sub-list
List<Object> ast = List.of(
    Symbol("if"),
    List.of(Symbol('<'), Symbol('x'), 0),
    "a",
    "b");

Architecture

Environment

Environment

(define x 10)

Environment

(define x 10)

Architecture

Evaluator

Evaluator

Is it an Atom?
0, 'a'

Return it

Evaluator

Is it a Symbol?
if, <

Look up in Environment
Return it

Evaluator

Is it a List?
(< x 0)

Evaluate all elements
Call function
Return result

Interlude

A program

@Zero("begin")
@Zero(list={@One("define"), @One("fizz-buzz"), @One(list={@Two("lambda"),
  @Two(list=@Three("n")), @Two(list={@Three("cond"),
    @Three(list={@Four("="), @Four(list={@Five("%"), @Five("n"),
      @Five("15")}), @Four("0")}), @Three("'fizzbuzz'"),
    @Three(list={@Four("="), @Four(list={@Five("%"), @Five("n"),
      @Five("3")}), @Four("0")}), @Three("'fizz'"),
    @Three(list={@Four("="), @Four(list={@Five("%"), @Five("n"),
      @Five("5")}), @Four("0")}), @Three("'buzz'"),
    @Three("else"), @Three("n")})})})
@Zero(list={@One("map"), @One("println"), @One(list={@Two("map"),
  @Two("fizz-buzz"), @Two(list={@Three("range"),
  @Three("1"), @Three("101")})})})
public class FizzBuzz {}

A program

Tokenizer


  (begin
    (define fizz-buzz (lambda (n)
      (cond (= (% n 15) 0) 'fizzbuzz')
      (cond (= (% n 3) 0) 'fizz')
      (cond (= (% n 5) 0) 'buzz')
      (else n))
    (map println (map fizz-buzz (range 1 101))))

Tokenizer

String code = """
  (begin
    (define fizz-buzz (lambda (n)
      (cond (= (% n 15) 0) 'fizzbuzz')
      (cond (= (% n 3) 0) 'fizz')
      (cond (= (% n 5) 0) 'buzz')
      (else n))
    (map println (map fizz-buzz (range 1 101))))""";
return code.split(" ");

Code generator

Taking it

TOO FAR

MetaScript

MetaScript

Tokenizer

Tokenizer

  • Unit tests written in Java, not AnnotationScript

  • AnnotationScript evaluates to regular Java objects

Tokenizer

Unit tests: identical!

MetaScript

Parser

Parser

Unit tests: identical!

MetaScript

Evaluator

Evaluator

Demo

What’s next

Next

🤷🏻

Next

Stack overflow errors

Next

Error handling

Next

String.split

Next

Spring integration

🤪

Conclusion

Conclusion

Learn about annotations

Conclusion

Learn about LISP

Conclusion

It was fun

Conclusion

Greenspun’s Tenth Rule:

Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.

Questions?

jqno.nl/talks/bullshitlanguage

#bullshitlanguage

image credits: see website