Commit 848259cf authored by jneen's avatar jneen

restore the tulip source

parent 44c30db7
cflags = --std=c11 -Isrc/ -g -lm -gdwarf-2 -g3 -lLLVM -l"lua5.2"
libs = $(shell find src -name '*.c')
main = ./src/main.c
BINARY = ./build/tulip
# -*- configurable options -*- #
EXTERNALS ?= ./.externals
PYTHON ?= python2
BINARY = ./build/tulip
TEST = ./build/tulip-test
BUILD_OPTS ?= --thread --no-shared --gcrootfinder=shadowstack
JIT_OPTS ?= --opt=jit
.PHONY: build run clean help
TARGET ?= target.py
build: binary
run: $(BINARY)
./build/tulip
# -*- constants -*- #
PYPY_DIR=$(EXTERNALS)/pypy
PYPY_TARGET=$(PYPY_DIR)/.make-target
PYTHONPATH = $$PYTHONPATH:$(PYPY_DIR)
RPYTHON_EXEC = $(PYTHON) $(PYPY_DIR)/rpython/bin/rpython $(BUILD_OPTS)
PYTHON_EXEC = PYTHONPATH=$(PYTHONPATH) $(PYTHON)
debug: $(BINARY)
gdb $^
# -*- default help action -*- #
.PHONY: help
help:
@echo "make help - display this message"
@echo "make build - build the interpreter"
@echo "make fetch-externals - download and unpack external deps"
@echo "make run - run the Tulip repl"
@echo "make run-interpreted - run the Tulip repl, interpreted through python"
@echo "make clean - remove build files"
@echo "make pyrepl - run a python repl with the correct paths set"
clean:
rm build/**
# -*- externals -*- #
.PHONY: fetch-externals
fetch-externals: $(PYPY_TARGET)
help:
echo "targets: build, run, clean"
.PHONY: pyrepl
pyrepl:
$(PYTHON_EXEC)
$(PYPY_TARGET):
mkdir -p $(dir $@)
curl https://bitbucket.org/pypy/pypy/get/default.tar.bz2 \
| tar -xjf - -C $(EXTERNALS)/pypy --strip-components=1
touch $@
# -*- build -*- #
DIST_BIN = ./dist/bin/tulip
CLEAN += $(DIST_BIN)
SOURCE = $(TARGET) $(shell find tulip -name '*.py')
.PHONY: binary
binary: $(BINARY)
$(DIST_BIN): $(PYPY_TARGET) $(SOURCE)
mkdir -p $(dir $@)
$(RPYTHON_EXEC) $(TARGET) && mv target-c $@
$(BINARY): $(srcs) $(libs) $(main)
mkdir -p build
clang $(cflags) $^ -o $@
.PHONY: build
build: $(DIST_BIN)
.PHONY: run
run: $(DIST_BIN)
$(DIST_BIN)
.PHONY: run-interpreted
run-interpreted: $(PYPY_TARGET)
$(PYTHON_EXEC) $(TARGET) $(ARGS)
.PHONY: clean
clean:
rm -rf $(CLEAN)
#!/usr/bin/env lua
local Stubs = require 'lua/stubs'
local Lexer = require 'lua/lexer'
local Skeleton = require 'lua/skeleton'
local Errors = require 'lua/errors'
local Macros = require 'lua/macros'
local Export = require 'lua/export'
local string_reader = Stubs.string_reader
local parse_skeleton = Skeleton.parse_skeleton
while true do
io.stdout:write('> ')
local input = io.stdin:read()
if not input then break end
local reader = string_reader(input)
local errors, out = Export.compile(reader)
if #errors == 0 then
print('parsed: ' .. Stubs.inspect_value(out.skel))
print('expanded: ' .. Stubs.inspect_value(out.expanded))
else
for _,e in pairs(errors) do
print('error: ' .. Stubs.inspect_value(e))
end
end
end
This diff is collapsed.
local Stubs = require('lua/stubs')
local state = {}
local function error_scope(fn)
local errors = {}
local orig_errors = state.errors
state.errors = errors
local out = fn()
state.errors = orig_errors
return errors, out
end
local function any()
return #state.errors > 0
end
local function ok()
return (not any())
end
local function error(error_tag, ...)
error_obj = tag(error_tag, ...)
table.insert(state.errors, error_obj)
return error_obj
end
return {
error_scope = error_scope,
error = error,
any = any,
ok = ok
}
local Stubs = require 'lua/stubs'
local Lexer = require 'lua/lexer'
local Skeleton = require 'lua/skeleton'
local Errors = require 'lua/errors'
local Macros = require 'lua/macros'
local Compiler = require 'lua/compiler'
function compile(reader)
local lexer = Lexer.new(reader)
local out = {}
local compiler = Compiler.compiler()
local errors, _ = Errors.error_scope(function()
out.skel = parse_skeleton(lexer)
if Errors.ok() then out.expanded = Macros.macro_expand(out.skel) end
if Errors.ok() then out.compiled = compiler.compile_root_expr(List.head(out.expanded)) end
end)
return errors, out
end
local function compile_module(reader)
local lexer = Lexer.new(reader)
local out = {}
local compiler = Compiler.compiler()
local errors, _ = Errors.error_scope(function()
out.skel = parse_skeleton(lexer)
if Errors.ok() then out.expanded = Macros.macro_expand(out.skel) end
if Errors.ok() then out.compiled = compiler.compile_root_module(out.expanded) end
end)
return errors, out
end
local function repl()
local state = { line = 0 }
while true do
state.line = state.line + 1
io.stdout:write('> ')
local input = io.stdin:read()
if not input then break end
local reader = Stubs.string_reader('<repl:' .. state.line .. '>', input)
local errors, out = compile(reader)
if #errors == 0 then
print('parsed: ' .. Stubs.inspect_value(out.skel))
print('expanded: ' .. Stubs.inspect_value(out.expanded))
print('compiled: ' .. Stubs.inspect_value(out.compiled))
else
for _,e in pairs(errors) do
print('error: ' .. Stubs.inspect_value(e))
end
end
end
end
local function test_file()
-- these should all compile correctly
-- local input = "@module Foo [ bar = 1; baz = 2; zot = bar > baz ]"
local input = "foo = 1; @module Bar [ baz = foo ]"
local reader = Stubs.string_reader('input.tlp', input)
print('compiling: ', inspect_value(input))
local errors, out = compile_module(reader)
if #errors == 0 then
print('parsed: ' .. Stubs.inspect_value(out.skel))
print('expanded: ' .. Stubs.inspect_value(out.expanded))
print('compiled: ' .. Stubs.inspect_value(out.compiled.members))
else
for _,e in pairs(errors) do
print('error: ' .. Stubs.inspect_value(e))
end
end
end
_G.init = function()
-- pass
end
local function flatten_modules(module)
local out = {}
local function go(module)
local assigns = {}
List.each(module.members, function(member)
if matches_tag(member, 'assign', 2) then
local name = tag_get(member, 0)
local body = tag_get(member, 1)
local marshalled = to_host_tree(body)
table.insert(assigns, {name=name,body=marshalled})
elseif member.name then
go(member)
end
end)
table.insert(out, {name = module.name, members = assigns})
end
go(module)
return out
end
_G.compile_file = function(file_name)
local file = io.open(file_name, 'r')
local input = file:read('*all')
local reader = Stubs.string_reader(file_name, input)
print('compiling: ', inspect_value(input))
local errors, out = compile_module(reader)
if #errors == 0 then
print('parsed: ' .. Stubs.inspect_value(out.skel))
print('expanded: ' .. Stubs.inspect_value(out.expanded))
print('compiled: ' .. Stubs.inspect_value(out.compiled))
local main_module = out.compiled
local flattened = flatten_modules(main_module)
print('flattened: ' .. Stubs.inspect_value(flattened))
return #flattened,flattened
else
for _,e in pairs(errors) do
print('error: ' .. Stubs.inspect_value(e))
end
end
end
return {
compile = compile
}
local host = {}
host.reader = {}
host.reader.setup = __compiler_reader_setup
host.reader.teardown = __compiler_reader_teardown
host.reader.next = __compiler_reader_next
host.value = {}
host.value.create_tag = __compiler_create_tag
host.value.tag_get = __compiler_tag_get
host.value.matches_tag = __compiler_matches_tag
host.value.inspect_value = __compiler_inspect_value
return host
local Stubs = require 'lua/stubs'
local empty = tag('nil')
local tag = Stubs.tag
local function cons(head, tail)
return tag('cons', head, tail)
end
local function is_cons(list)
return Stubs.matches_tag(list, 'cons', 2)
end
local function is_nil(list)
return Stubs.matches_tag(list, 'nil', 0)
end
local function head(list)
return tag_get(list, 0)
end
local function tail(list)
return tag_get(list, 1)
end
local function is_singleton(list)
return is_cons(list) and is_nil(tail(list))
end
local function list(tbl)
local out = empty
for i = #tbl,1,-1 do
out = cons(tbl[i], out)
end
return out
end
local function each(list, fn)
while matches_tag(list, 'cons', 2) do
fn(head(list))
list = tail(list)
end
end
local function map_reverse(list, fn)
local out = empty
each(list, function(el)
out = cons(fn(el), out)
end)
return out
end
local function foldl(list, accum, fn)
if is_nil(list) then return accum end
return foldl(tail(list), fn(accum, head(list)), fn)
end
local function foldr(list, init, fn)
if is_nil(list) then return init end
return fn(head(list), foldr(tail(list), init, fn))
end
local function reverse(list)
return map_reverse(list, function(x) return x end)
end
local function map(list, fn)
return reverse(map_reverse(list, fn))
end
local function mapi(list, fn)
local i = 0
return map(list, function(e)
local out = fn(e, i)
i = i + 1
return out
end)
end
local function join(list, join_str)
if is_nil(list) then return '' end
local out = head(list)
each(tail(list), function(el)
out = out .. join_str .. el
end)
return out
end
local function each_slice(list, pred, fn)
local last = empty
each(list, function(el)
if pred(el) then
fn(reverse(last))
last = empty
else
last = cons(el, last)
end
end)
fn(last)
end
local function split(list, pred)
local out = empty
each_slice(list, pred, function(slice)
out = cons(slice, out)
end)
return reverse(out)
end
function split_once(list, pred)
local out = empty
while is_cons(list) do
local h, t = head(list), tail(list)
if pred(h) then
return reverse(out), t
else
out = cons(h, out)
list = t
end
end
return nil, nil
end
local function size(list)
local count = 0
each(list, function(_) count = count + 1 end)
return count
end
local function find_map(list, pred)
while not is_nil(list) do
local out = pred(head(list))
if out then return out end
list = tail(list)
end
end
local function find(list, pred)
return find_map(list, function(e) if pred(e) then return e end end)
end
local function to_table(list)
local out = {}
each(list, function(el)
table.append(out, el)
end)
return out
end
Stubs.impl_inspect_tag('nil', 0, function() return '\\list()' end)
Stubs.impl_inspect_tag('cons', 2, function(head, tail)
local inspects = map(cons(head, tail), Stubs.inspect_value)
return '\\list(' .. join(inspects, ' ') .. ')'
end)
_G.List = {
list = list,
map = map,
mapi = mapi,
find = find,
find_map = find_map,
cons = cons,
reverse = reverse,
empty = empty,
head = head,
tail = tail,
each = each,
is_cons = is_cons,
is_nil = is_nil,
join = join,
each_slice = each_slice,
split = split,
split_once = split_once,
size = size,
foldl = foldl,
foldr = foldr,
is_singleton = is_singleton,
to_table = to_table,
}
return List
local Stubs = require 'lua/stubs'
local Lexer = require 'lua/lexer'
local Skeleton = require 'lua/skeleton'
local List = require 'lua/list'
local Lexer = require 'lua/lexer'
local tag = Stubs.tag
local tag_get = Stubs.tag_get
local function synthetic_token(name, value)
return Stubs.Token(Lexer.token_ids[name], value, '<synthetic>')
end
local tok = synthetic_token
local macro_registry = {}
local function define_builtin_macro(name, impl)
if macro_registry[name] then error('macro ' .. name .. ' already exists!') end
macro_registry[name] = impl
end
local function is_token(skel, toktype)
if not matches_tag(skel, 'skeleton/token', 1) then return false end
return check_tok(tag_get(skel, 0), Lexer.token_ids[toktype])
end
local function brace(items)
return tag('skeleton/nested', tok('LBRACE', nil), tok('RBRACE', nil), items)
end
define_builtin_macro('list', function(skels)
return List.foldr(skels, tag('token', tok('TAGGED', 'nil')), function(el, next)
return tag('skeleton/nested', tok('LBRACE', nil), tok('RBRACE', nil),
List.list{tag('token', tok('TAGGED', 'cons')),
el,
next})
end)
end)
local function macro_use(skel)
if not matches_tag(skel, 'skeleton/nested', 3) then return nil end
local open_tok = tag_get(skel, 0)
if check_tok(open_tok, Lexer.token_ids.MACRO) then
return tokvalue(open_tok)
else
return nil
end
end
local found_macro_state = {}
local function replace_macros(skels)
return List.map(skels, function(skel)
local macro_name = macro_use(skel)
if macro_name then
found_macro_state.value = true
return macro_registry[macro_name](tag_get(skel, 2))
elseif matches_tag(skel, 'skeleton/nested', 3) then
return tag('skeleton/nested', tag_get(skel, 0),
tag_get(skel, 1),
replace_macros(tag_get(skel, 2)))
elseif matches_tag(skel, 'skeleton/item', 2) then
return tag('skeleton/item', replace_macros(tag_get(skel, 0)),
replace_macros(tag_get(skel, 1)))
elseif matches_tag(skel, 'skeleton/annotation', 1) then
return tag('skeleton/annotation', replace_macros(tag_get(skel, 0)))
else
return skel
end
end)
end
-- forward decl
local macro_expand
local function macro_expand_1(skels)
found_macro_state.value = false
local out = replace_macros(skels)
return found_macro_state.value, out
end
function macro_expand(skels)
local modified = true
while modified do
modified, skels = macro_expand_1(skels)
end
return skels
end
return {
macro_expand_1 = macro_expand_1,
macro_expand = macro_expand,
synthetic_token = synthetic_token,
}
local Stubs = require('lua/stubs')
local Lexer = require('lua/lexer')
local Errors = require('lua/errors')
local List = require('lua/list')
token_ids = Lexer.token_ids
token_names = Lexer.token_names
eats_preceding_newline = {
[token_ids.GT] = true,
[token_ids.RARROW] = true,
[token_ids.EQ] = true,
[token_ids.COMMA] = true,
[token_ids.PIPE] = true,
[token_ids.QUESTION] = true,
[token_ids.RBRACK] = true,
[token_ids.RBRACE] = true,
}
function parse_skeleton(lexer)
lexer.setup()
local parsed = _parse_sequence(lexer, nil, 0)
lexer.teardown()
return parsed
end
function is_closing(tok)
return check_tok(tok, token_ids.RBRACK) or
check_tok(tok, token_ids.RBRACE) or
check_tok(tok, token_ids.RPAREN)
end
function unexpected(token, matching, message)
print('unexpected', token, message)
return tag('error', Errors.error('parse/unexpected', token, matching, message))
end
function unmatched(token, message)
print('unmatched', token, message)
return tag('error', Errors.error('parse/unmatched', token, message))
end
local function inspect_items(items)
local inners = List.map(items, inspect_value)
return List.join(inners, ' ')
end
Stubs.impl_inspect_tag('skeleton/nested', 3, function(open, close, body)
local body_ = inspect_items(body)
return '\\skel[' .. inspect_value(open) .. ': ' .. body_ .. ' :' .. inspect_value(close) .. ']'
end)
Stubs.impl_inspect_tag('skeleton/token', 1, function(tok)
return inspect_value(tok)
end)
Stubs.impl_inspect_tag('skeleton/item', 2, function(annotations, body)
local anns_ = List.join(List.map(annotations, inspect_value), '; ')
local body_ = List.join(List.map(body, inspect_value), ' ')
return '(' .. anns_ .. ':' .. body_ .. ':)'
end)
function _parse_sequence(lexer, open_tok, expected_close_id)
local elements = {}
local items = {}
local current_annotation = nil
local annotations = {}
local function flush_item()
if expected_close_id == token_ids.RPAREN then return end
if #elements == 0 then return end
table.insert(items, tag('skeleton/item', List.list(annotations), List.list(elements)))
elements = {}
annotations = {}
end
local function flush_annotation()
if current_annotation then
table.insert(annotations, tag('skeleton/annotation', List.list(elements)))
elements = {}
end
end
local function final_items()
if expected_close_id == token_ids.RPAREN then
return List.list(elements)
else
flush_item()