about summary refs log tree commit diff stats
path: root/mu.vim
blob: 83a1f683e04d3db57c15a0320cc2be54d1d42fc6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
" Vim syntax file
" Language:    mu
" Maintainer:  Kartik Agaram <mu@akkartik.com>
" URL:         http://github.com/akkartik/mu
" License:     public domain
"
" Copy this into your ftplugin directory, and add the following to your vimrc
" or to .vim/ftdetect/mu.vim:
"   autocmd BufReadPost,BufNewFile *.mu set filetype=mu

let s:save_cpo = &cpo
set cpo&vim

" todo: why does this periodically lose syntax, like on file reload?
"   $ vim x.mu
"   :e
"? if exists("b:syntax")
"?   finish
"? endif
"? let b:syntax = "mu"

setlocal iskeyword=@,48-57,?,!,_,$,-
setlocal formatoptions-=t  " Mu programs have long lines
setlocal formatoptions+=c  " but comments should still wrap

syntax match muComment /#.*$/ | highlight link muComment Comment
syntax match muSalientComment /##.*$/ | highlight link muSalientComment SalientComment
syntax match muComment /;.*$/ | highlight link muComment Comment
syntax match muSalientComment /;;.*$/ | highlight link muSalientComment SalientComment
set comments+=n:#
syntax match CommentedCode "#? .*"
let b:cmt_head = "#? "

" Mu strings are inside [ ... ] and can span multiple lines
" don't match '[' at end of line, that's usually code
syntax match muLiteral %^[^ a-zA-Z0-9(){}\[\]#$_*@&,=-][^ ,]*\|[ ,]\@<=[^ a-zA-Z0-9(){}\[\]#$_*@&,=-][^ ,]*%
syntax region muString start=+\[[^\]]+ end=+\]+
syntax match muString "\[\]"
highlight link muString String
" Mu syntax for representing the screen in scenarios
syntax region muScreen start=+ \.+ end=+\.$\|$+
highlight link muScreen muString

" Mu literals
syntax match muLiteral %[^ ]\+:literal/[^ ,]*\|[^ ]\+:literal\>%
syntax match muLiteral %\<[0-9-]\?[0-9]\+/[^ ,]*%
syntax match muLiteral % [0-9-]\?[0-9]\+[, ]\@=\| [0-9-]\?[0-9]\+$%
syntax match muLiteral "^\s\+[^ 0-9a-zA-Z{}$#\[\]][^ ]*\s*$"
" labels
syntax match muLiteral %[^ ]\+:label/[^ ,]*\|[^ ]\+:label\>%
" other literal types
syntax match muLiteral %[^ ]\+:type/[^ ,]*\|[^ ]\+:type\>%
syntax match muLiteral %[^ ]\+:offset/[^ ,]*\|[^ ]\+:offset\>%
syntax match muLiteral %[^ ]\+:variant/[^ ,]*\|[^ ]\+:variant\>%
highlight link muLiteral Constant
syntax keyword muKeyword default-space new-default-space local-scope next-ingredient ingredient rewind-ingredients load-ingredients | highlight link muKeyword Constant

syntax match muDelimiter "[{}]" | highlight link muDelimiter Delimiter
syntax match muAssign "<-"
syntax match muAssign "\<raw\>"
highlight link muAssign SpecialChar
syntax match muGlobal %[^ ]\+:global/\?[^ ,]*% | highlight link muGlobal SpecialChar
syntax keyword muControl reply reply-if reply-unless return return-if return-unless jump jump-if jump-unless loop loop-if loop-unless break break-if break-unless current-continuation continue-from create-delimited-continuation reply-delimited-continuation | highlight muControl ctermfg=3
" common keywords
syntax match muRecipe "^recipe\>\|^recipe!\>\|^def\>\|^def!\>\|^before\>\|^after\>\| -> " | highlight muRecipe ctermfg=208
syntax match muScenario "^scenario\>" | highlight muScenario ctermfg=34
syntax match muPendingScenario "^pending-scenario\>" | highlight link muPendingScenario SpecialChar
syntax match muData "^type\>\|^container\>\|^exclusive-container\>" | highlight muData ctermfg=226

let &cpo = s:save_cpo
'n531' href='#n531'>531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
## experimental compiler to translate programs written in a generic
## expression-oriented language called 'lambda' into Mu

# incomplete; code generator not done
# potential enhancements:
#   symbol table
#   poor man's macros
#     substitute one instruction with multiple, parameterized by ingredients and products

scenario convert-lambda [
  run [
    local-scope
    1:text/raw <- lambda-to-mu [(add a (multiply b c))]
    2:@:char/raw <- copy *1:text/raw
  ]
  memory-should-contain [
    2:array:character <- [t1 <- multiply b c
result <- add a t1]
  ]
]

def lambda-to-mu in:text -> out:text [
  local-scope
  load-ingredients
  out <- copy 0
  cells:&:cell <- parse in
  out <- to-mu cells
]

# 'parse' will turn lambda expressions into trees made of cells
exclusive-container cell [
  atom:text
  pair:pair
]

# printed below as < first | rest >
container pair [
  first:&:cell
  rest:&:cell
]

def new-atom name:text -> result:&:cell [
  local-scope
  load-ingredients
  result <- new cell:type
  *result <- merge 0/tag:atom, name
]

def new-pair a:&:cell, b:&:cell -> result:&:cell [
  local-scope
  load-ingredients
  result <- new cell:type
  *result <- merge 1/tag:pair, a/first, b/rest
]

def is-atom? x:&:cell -> result:bool [
  local-scope
  load-ingredients
  return-unless x, 0/false
  _, result <- maybe-convert *x, atom:variant
]

def is-pair? x:&:cell -> result:bool [
  local-scope
  load-ingredients
  return-unless x, 0/false
  _, result <- maybe-convert *x, pair:variant
]

scenario atom-is-not-pair [
  local-scope
  s:text <- new [a]
  x:&:cell <- new-atom s
  10:bool/raw <- is-atom? x
  11:bool/raw <- is-pair? x
  memory-should-contain [
    10 <- 1
    11 <- 0
  ]
]

scenario pair-is-not-atom [
  local-scope
  # construct (a . nil)
  s:text <- new [a]
  x:&:cell <- new-atom s
  y:&:cell <- new-pair x, 0/nil
  10:bool/raw <- is-atom? y
  11:bool/raw <- is-pair? y
  memory-should-contain [
    10 <- 0
    11 <- 1
  ]
]

def atom-match? x:&:cell, pat:text -> result:bool [
  local-scope
  load-ingredients
  s:text, is-atom?:bool <- maybe-convert *x, atom:variant
  return-unless is-atom?, 0/false
  result <- equal pat, s
]

scenario atom-match [
  local-scope
  x:&:cell <- new-atom [abc]
  10:bool/raw <- atom-match? x, [abc]
  memory-should-contain [
    10 <- 1
  ]
]

def first x:&:cell -> result:&:cell [
  local-scope
  load-ingredients
  pair:pair, pair?:bool <- maybe-convert *x, pair:variant
  return-unless pair?, 0/nil
  result <- get pair, first:offset
]

def rest x:&:cell -> result:&:cell [
  local-scope
  load-ingredients
  pair:pair, pair?:bool <- maybe-convert *x, pair:variant
  return-unless pair?, 0/nil
  result <- get pair, rest:offset
]

def set-first base:&:cell, new-first:&:cell -> base:&:cell [
  local-scope
  load-ingredients
  pair:pair, is-pair?:bool <- maybe-convert *base, pair:variant
  return-unless is-pair?
  pair <- put pair, first:offset, new-first
  *base <- merge 1/pair, pair
]

def set-rest base:&:cell, new-rest:&:cell -> base:&:cell [
  local-scope
  load-ingredients
  pair:pair, is-pair?:bool <- maybe-convert *base, pair:variant
  return-unless is-pair?
  pair <- put pair, rest:offset, new-rest
  *base <- merge 1/pair, pair
]

scenario cell-operations-on-atom [
  local-scope
  s:text <- new [a]
  x:&:cell <- new-atom s
  10:&:cell/raw <- first x
  11:&:cell/raw <- rest x
  memory-should-contain [
    10 <- 0  # first is nil
    11 <- 0  # rest is nil
  ]
]

scenario cell-operations-on-pair [
  local-scope
  # construct (a . nil)
  s:text <- new [a]
  x:&:cell <- new-atom s
  y:&:cell <- new-pair x, 0/nil
  x2:&:cell <- first y
  10:bool/raw <- equal x, x2
  11:&:cell/raw <- rest y
  memory-should-contain [
    10 <- 1  # first is correct
    11 <- 0  # rest is nil
  ]
]

## convert lambda text to a tree of cells

def parse in:text -> out:&:cell [
  local-scope
  load-ingredients
  s:&:stream:char <- new-stream in
  out, s <- parse s
  trace 2, [app/parse], out
]

def parse in:&:stream:char -> out:&:cell, in:&:stream:char [
  local-scope
  load-ingredients
  # skip whitespace
  in <- skip-whitespace in
  c:char, eof?:bool <- peek in
  return-if eof?, 0/nil
  pair?:bool <- equal c, 40/open-paren
  {
    break-if pair?
    # atom
    buf:&:buffer:char <- new-buffer 30
    {
      done?:bool <- end-of-stream? in
      break-if done?
      # stop before close paren or space
      c:char <- peek in
      done? <- equal c, 41/close-paren
      break-if done?
      done? <- space? c
      break-if done?
      c <- read in
      buf <- append buf, c
      loop
    }
    s:text <- buffer-to-array buf
    out <- new-atom s
  }
  {
    break-unless pair?
    # pair
    read in  # skip the open-paren
    out <- new cell:type  # start out with nil
    # read in first element of pair
    {
      end?:bool <- end-of-stream? in
      not-end?:bool <- not end?
      assert not-end?, [unbalanced '(' in expression]
      c <- peek in
      close-paren?:bool <- equal c, 41/close-paren
      break-if close-paren?
      first:&:cell, in <- parse in
      *out <- merge 1/pair, first, 0/nil
    }
    # read in any remaining elements
    curr:&:cell <- copy out
    {
      in <- skip-whitespace in
      end?:bool <- end-of-stream? in
      not-end?:bool <- not end?
      assert not-end?, [unbalanced '(' in expression]
      # termination check: ')'
      c <- peek in
      {
        close-paren?:bool <- equal c, 41/close-paren
        break-unless close-paren?
        read in  # skip ')'
        break +end-pair
      }
      # still here? read next element of pair
      next:&:cell, in <- parse in
      is-dot?:bool <- atom-match? next, [.]
      {
        break-if is-dot?
        next-curr:&:cell <- new-pair next, 0/nil
        curr <- set-rest curr, next-curr
        curr <- rest curr
      }
      {
        break-unless is-dot?
        # deal with dotted pair
        in <- skip-whitespace in
        c <- peek in
        not-close-paren?:bool <- not-equal c, 41/close-paren
        assert not-close-paren?, [')' cannot immediately follow '.']
        final:&:cell <- parse in
        curr <- set-rest curr, final
        # we're not gonna update curr, so better make sure the next iteration
        # is going to end the pair
        in <- skip-whitespace in
        c <- peek in
        close-paren?:bool <- equal c, 41/close-paren
        assert close-paren?, ['.' must be followed by exactly one expression before ')']
      }
      loop
    }
    +end-pair
  }
]

def skip-whitespace in:&:stream:char -> in:&:stream:char [
  local-scope
  load-ingredients
  {
    done?:bool <- end-of-stream? in
    return-if done?, 0/null
    c:char <- peek in
    space?:bool <- space? c
    break-unless space?
    read in  # skip
    loop
  }
]

def to-text x:&:cell -> out:text [
  local-scope
  load-ingredients
  buf:&:buffer:char <- new-buffer 30
  buf <- to-buffer x, buf
  out <- buffer-to-array buf
]

def to-buffer x:&:cell, buf:&:buffer:char -> buf:&:buffer:char [
  local-scope
  load-ingredients
  # base case: empty cell
  {
    break-if x
    buf <- append buf, [<>]
    return
  }
  # base case: atom
  {
    s:text, atom?:bool <- maybe-convert *x, atom:variant
    break-unless atom?
    buf <- append buf, s
    return
  }
  # recursive case: pair
  buf <- append buf, [< ]
  first:&:cell <- first x
  buf <- to-buffer first, buf
  buf <- append buf, [ | ]
  rest:&:cell <- rest x
  buf <- to-buffer rest, buf
  buf <- append buf, [ >]
]

scenario parse-single-letter-atom [
  local-scope
  s:text <- new [a]
  x:&:cell <- parse s
  s2:text, 10:bool/raw <- maybe-convert *x, atom:variant
  11:@:char/raw <- copy *s2
  memory-should-contain [
    10 <- 1  # parse result is an atom
    11:array:character <- [a]
  ]
]

scenario parse-atom [
  local-scope
  s:text <- new [abc]
  x:&:cell <- parse s
  s2:text, 10:bool/raw <- maybe-convert *x, atom:variant
  11:@:char/raw <- copy *s2
  memory-should-contain [
    10 <- 1  # parse result is an atom
    11:array:character <- [abc]
  ]
]

scenario parse-list-of-two-atoms [
  local-scope
  s:text <- new [(abc def)]
  x:&:cell <- parse s
  trace-should-contain [
    app/parse: < abc | < def | <> > >
  ]
  10:bool/raw <- is-pair? x
  x1:&:cell <- first x
  x2:&:cell <- rest x
  s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
  12:bool/raw <- is-pair? x2
  x3:&:cell <- first x2
  s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
  14:&:cell/raw <- rest x2
  20:@:char/raw <- copy *s1
  30:@:char/raw <- copy *s2
  memory-should-contain [
    10 <- 1  # parse result is a pair
    11 <- 1  # result.first is an atom
    12 <- 1  # result.rest is a pair
    13 <- 1  # result.rest.first is an atom
    14 <- 0  # result.rest.rest is nil
    20:array:character <- [abc]  # result.first
    30:array:character <- [def]  # result.rest.first
  ]
]

scenario parse-list-with-extra-spaces [
  local-scope
  s:text <- new [ ( abc  def ) ]  # extra spaces
  x:&:cell <- parse s
  trace-should-contain [
    app/parse: < abc | < def | <> > >
  ]
  10:bool/raw <- is-pair? x
  x1:&:cell <- first x
  x2:&:cell <- rest x
  s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
  12:bool/raw <- is-pair? x2
  x3:&:cell <- first x2
  s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
  14:&:cell/raw <- rest x2
  20:@:char/raw <- copy *s1
  30:@:char/raw <- copy *s2
  memory-should-contain [
    10 <- 1  # parse result is a pair
    11 <- 1  # result.first is an atom
    12 <- 1  # result.rest is a pair
    13 <- 1  # result.rest.first is an atom
    14 <- 0  # result.rest.rest is nil
    20:array:character <- [abc]  # result.first
    30:array:character <- [def]  # result.rest.first
  ]
]

scenario parse-list-of-more-than-two-atoms [
  local-scope
  s:text <- new [(abc def ghi)]
  x:&:cell <- parse s
  trace-should-contain [
    app/parse: < abc | < def | < ghi | <> > > >
  ]
  10:bool/raw <- is-pair? x
  x1:&:cell <- first x
  x2:&:cell <- rest x
  s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
  12:bool/raw <- is-pair? x2
  x3:&:cell <- first x2
  s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
  x4:&:cell <- rest x2
  14:bool/raw <- is-pair? x4
  x5:&:cell <- first x4
  s3:text, 15:bool/raw <- maybe-convert *x5, atom:variant
  16:&:cell/raw <- rest x4
  20:@:char/raw <- copy *s1
  30:@:char/raw <- copy *s2
  40:@:char/raw <- copy *s3
  memory-should-contain [
    10 <- 1  # parse result is a pair
    11 <- 1  # result.first is an atom
    12 <- 1  # result.rest is a pair
    13 <- 1  # result.rest.first is an atom
    14 <- 1  # result.rest.rest is a pair
    15 <- 1  # result.rest.rest.first is an atom
    16 <- 0  # result.rest.rest.rest is nil
    20:array:character <- [abc]  # result.first
    30:array:character <- [def]  # result.rest.first
    40:array:character <- [ghi]  # result.rest.rest
  ]
]

scenario parse-nested-list [
  local-scope
  s:text <- new [((abc))]
  x:&:cell <- parse s
  trace-should-contain [
    app/parse: < < abc | <> > | <> >
  ]
  10:bool/raw <- is-pair? x
  x1:&:cell <- first x
  11:bool/raw <- is-pair? x
  x2:&:cell <- first x1
  s1:text, 12:bool/raw <- maybe-convert *x2, atom:variant
  13:&:cell/raw <- rest x1
  14:&:cell/raw <- rest x
  20:@:char/raw <- copy *s1
  memory-should-contain [
    10 <- 1  # parse result is a pair
    11 <- 1  # result.first is a pair
    12 <- 1  # result.first.first is an atom
    13 <- 0  # result.first.rest is nil
    14 <- 0  # result.rest is nil
    20:array:character <- [abc]  # result.first.first
  ]
]

scenario parse-nested-list-2 [
  local-scope
  s:text <- new [((abc) def)]
  x:&:cell <- parse s
  trace-should-contain [
    app/parse: < < abc | <> > | < def | <> > >
  ]
  10:bool/raw <- is-pair? x
  x1:&:cell <- first x
  11:bool/raw <- is-pair? x
  x2:&:cell <- first x1
  s1:text, 12:bool/raw <- maybe-convert *x2, atom:variant
  13:&:cell/raw <- rest x1
  x3:&:cell <- rest x
  x4:&:cell <- first x3
  s2:text, 14:bool/raw <- maybe-convert *x4, atom:variant
  15:&:cell/raw <- rest x3
  20:@:char/raw <- copy *s1
  30:@:char/raw <- copy *s2
  memory-should-contain [
    10 <- 1  # parse result is a pair
    11 <- 1  # result.first is a pair
    12 <- 1  # result.first.first is an atom
    13 <- 0  # result.first.rest is nil
    14 <- 1  # result.rest.first is an atom
    15 <- 0  # result.rest.rest is nil
    20:array:character <- [abc]  # result.first.first
    30:array:character <- [def]  # result.rest.first
  ]
]

# todo: uncomment these tests after we figure out how to continue tests after
# assertion failures
#? scenario parse-error [
#?   local-scope
#?   s:text <- new [(]
#? #?   hide-errors
#?   x:&:cell <- parse s
#? #?   show-errors
#?   trace-should-contain [
#?     error: unbalanced '(' in expression
#?   ]
#? ]
#? 
#? scenario parse-error-after-element [
#?   local-scope
#?   s:text <- new [(abc]
#? #?   hide-errors
#?   x:&:cell <- parse s
#? #?   show-errors
#?   trace-should-contain [
#?     error: unbalanced '(' in expression
#?   ]
#? ]

scenario parse-dotted-list-of-two-atoms [
  local-scope
  s:text <- new [(abc . def)]
  x:&:cell <- parse s
  trace-should-contain [
    app/parse: < abc | def >
  ]
  10:bool/raw <- is-pair? x
  x1:&:cell <- first x
  x2:&:cell <- rest x
  s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
  s2:text, 12:bool/raw <- maybe-convert *x2, atom:variant
  20:@:char/raw <- copy *s1
  30:@:char/raw <- copy *s2
  memory-should-contain [
    # parses to < abc | def >
    10 <- 1  # parse result is a pair
    11 <- 1  # result.first is an atom
    12 <- 1  # result.rest is an atom
    20:array:character <- [abc]  # result.first
    30:array:character <- [def]  # result.rest
  ]
]

scenario parse-dotted-list-of-more-than-two-atoms [
  local-scope
  s:text <- new [(abc def . ghi)]
  x:&:cell <- parse s
  trace-should-contain [
    app/parse: < abc | < def | ghi > >
  ]
  10:bool/raw <- is-pair? x
  x1:&:cell <- first x
  x2:&:cell <- rest x
  s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
  12:bool/raw <- is-pair? x2
  x3:&:cell <- first x2
  s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
  x4:&:cell <- rest x2
  s3:text, 14:bool/raw <- maybe-convert *x4, atom:variant
  20:@:char/raw <- copy *s1
  30:@:char/raw <- copy *s2
  40:@:char/raw <- copy *s3
  memory-should-contain [
    10 <- 1  # parse result is a pair
    11 <- 1  # result.first is an atom
    12 <- 1  # result.rest is a pair
    13 <- 1  # result.rest.first is an atom
    14 <- 1  # result.rest.rest is an atom
    20:array:character <- [abc]  # result.first
    30:array:character <- [def]  # result.rest.first
    40:array:character <- [ghi]  # result.rest.rest
  ]
]

## convert tree of cells to Mu text

def to-mu in:&:cell -> out:text [
  local-scope
  load-ingredients
  buf:&:buffer:char <- new-buffer 30
  buf <- to-mu in, buf
  out <- buffer-to-array buf
]

def to-mu in:&:cell, buf:&:buffer:char -> buf:&:buffer:char, result-name:text [
  local-scope
  load-ingredients
  # null cell? no change.
  # pair with all atoms? gensym a new variable
  # pair containing other pairs? recurse
  result-name <- copy 0
]