Skip to content

Commit

Permalink
Merge pull request #87 from GlowingScrewdriver/strings
Browse files Browse the repository at this point in the history
Implement string literals
  • Loading branch information
chsasank committed Jul 25, 2024
2 parents 26a4cea + 93ae9d7 commit c79daf7
Show file tree
Hide file tree
Showing 20 changed files with 206 additions and 19 deletions.
24 changes: 22 additions & 2 deletions src/backend/brilisp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ def is_function(expr):
return isinstance(expr, list) and (expr[0] == "define")


def is_string(expr):
return isinstance(expr, list) and (expr[0] == "define-string")


def gen_string(expr):
assert expr[2][0] == "string"
return {
"name": expr[1],
"value": expr[2][1],
}


def gen_function(expr):
header = expr[1]
name = header[0][0]
Expand Down Expand Up @@ -100,6 +112,8 @@ def is_value(instr):
"fpext",
"fptrunc",
"bitcast",
# String reference
"string-ref",
}
return (instr[0] == "set") and (instr[2][0] in value_op)

Expand Down Expand Up @@ -184,9 +198,15 @@ def gen_store_instr(instr):
def brilisp(expr):
assert expr[0] == "brilisp"
body = expr[1:]
functions, strings = [], []
for x in body:
assert is_function(x), f"{x} is not a function"
return {"functions": [gen_function(x) for x in body]}
if is_function(x):
functions.append(gen_function(x))
elif is_string(x):
strings.append(gen_string(x))
else:
raise Exception(f"{x} is neither function nor string")
return {"functions": functions, "strings": strings}


def main():
Expand Down
41 changes: 37 additions & 4 deletions src/backend/c-lisp.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,30 @@ def __init__(self):
self.scopes = []
# Function name > (ret-type, (arg-types...))
self.function_types = {}
# String literals
self.string_literals = {}

def c_lisp(self, prog):
"""Entry point to C-Lisp compiler"""
if not prog[0] == "c-lisp":
raise CodegenError("Input not a C-Lisp program")

return ["brilisp"] + [self.gen_function(fn) for fn in prog[1:]]
brilisp_funcs = []
for defn in prog[1:]:
if defn[0] == "define":
brilisp_funcs.append(self.gen_function(defn))
else:
raise CodegenError(f"Not a function: {defn}")

brilisp_strings = [
["define-string", name, ["string", val]]
for name, val in self.string_literals.items()
]
return [
"brilisp",
*brilisp_strings,
*brilisp_funcs,
]

def construct_scoped_name(self, name, scopes):
return ".".join([name] + scopes)
Expand Down Expand Up @@ -334,6 +351,22 @@ def compile(self, expr):
return ExpressionResult(instructions, res_sym, res_type)


class StringExpression(Expression):
@classmethod
def is_valid_expr(cls, expr):
return expr[0] == "string"

def compile(self, expr):
if not verify_shape(expr, ["string", str]):
raise CodegenError(f"Invalid string literal: {expr}")

str_sym, res_sym = [random_label(CLISP_PREFIX) for i in range(2)]
self.ctx.string_literals[str_sym] = expr[1]
res_typ = ["ptr", "int8"]
instrs = [["set", [res_sym, res_typ], ["string-ref", str_sym]]]
return ExpressionResult(instrs, res_sym, res_typ)


class SetExpression(Expression):
@classmethod
def is_valid_expr(cls, expr):
Expand Down Expand Up @@ -445,9 +478,7 @@ def compile(self, expr):
allowed_types = self.op_codes[opcode][0]
match, expected_type = type_match(expr_obj.typ, allowed_types)
if not match:
raise CodegenError(
f"Operands to {opcode} must be {expected_type}"
)
raise CodegenError(f"Operands to {opcode} must be {expected_type}")
input_instr_list += expr_obj.instructions
operand_types.append(expr_obj.typ)
operand_syms.append(expr_obj.symbol)
Expand Down Expand Up @@ -612,6 +643,7 @@ def compile(self, expr):

return ExpressionResult(instrs, res_sym, res_type)


def type_match(typ, pattern):
"""
Verify type `typ` against `pattern`.
Expand All @@ -638,6 +670,7 @@ def type_match(typ, pattern):
else:
return typ == pattern, str(pattern)


if __name__ == "__main__":
brilisp_code_generator = BrilispCodeGenerator()
json.dump(brilisp_code_generator.c_lisp(json.loads(sys.stdin.read())), sys.stdout)
29 changes: 29 additions & 0 deletions src/backend/llvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def __init__(self):
self.func_bbs = {}

def generate(self, bril_prog):
for string in bril_prog.strings:
self.gen_string_defn(string)
for fn in bril_prog.functions:
self.gen_function(fn)

Expand Down Expand Up @@ -258,6 +260,21 @@ def gen_id(instr):
self.declare_var(self.gen_type(instr.type), instr.dest)
self.gen_symbol_store(instr.dest, self.gen_symbol_load(instr.args[0]))

def gen_string_ref(instr):
self.declare_var(self.gen_type(instr.type), instr.dest)
# Clang emits something like this, to get a character pointer to
# a string constant:
# store i8* getelementptr inbounds
# ([14 x i8], [14 x i8]* @.str, i64 0, i64 0),
# i8** %1, align 8
self.gen_symbol_store(
instr.dest,
self.builder.gep(
self.module.get_global(instr.args[0]),
[ir.Constant(ir.IntType(1), 0)] * 2,
),
)

for instr in instrs:
try:
if "label" in instr:
Expand Down Expand Up @@ -286,6 +303,8 @@ def gen_id(instr):
gen_ptradd(instr)
elif instr.op == "id":
gen_id(instr)
elif instr.op == "string-ref":
gen_string_ref(instr)
elif instr.op in value_ops:
gen_value(instr)
elif instr.op in cmp_ops:
Expand Down Expand Up @@ -387,6 +406,16 @@ def gen_function(self, fn):
self.builder.branch(bb_entry) # Cannot use implicit fallthroughs
return func

def gen_string_defn(self, string):
string_arr = bytearray(string.value + "\x00", encoding="UTF-8")
typ = ir.ArrayType(ir.IntType(8), len(string_arr))
global_var = ir.GlobalVariable(
module=self.module,
typ=typ,
name=string.name,
)
global_var.initializer = ir.Constant(typ, string_arr)


def main():
bril_prog = munch.munchify(json.load(sys.stdin))
Expand Down
8 changes: 4 additions & 4 deletions src/backend/prelisp.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def preprocess(expr, env):
if expr[0] == "unquote":
assert len(expr) == 2
return expand_macro(expr[1], env), "append"
elif expr[0] == 'unquote-splicing':
elif expr[0] == "unquote-splicing":
assert len(expr) == 2
out = expand_macro(expr[1], env)
assert isinstance(out, list)
Expand All @@ -26,11 +26,11 @@ def preprocess(expr, env):
res = []
for x in expr:
out, mode = preprocess(x, env)
if mode == 'append':
if mode == "append":
res.append(out)
else:
res.extend(out)

return res, "append"
else:
return expr, "append"
Expand All @@ -39,7 +39,7 @@ def preprocess(expr, env):
def expand_macro(expr, env):
if isinstance(expr, list):
fn_name, fn_args = expr[0], expr[1:]
fn_name = fn_name.replace('-', '_')
fn_name = fn_name.replace("-", "_")
fn = getattr(env, fn_name)
fn_args = [preprocess(x, env)[0] for x in fn_args]
expr_out = fn(*fn_args)
Expand Down
1 change: 1 addition & 0 deletions src/backend/tests/brilisp/string.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Strings are working!! 🥳
9 changes: 9 additions & 0 deletions src/backend/tests/brilisp/string.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(brilisp
(define ((puts int) (s (ptr int8))))

(define ((main void))
(set (tmp (ptr int8)) (string-ref message))
(set (tmp int) (call puts tmp))
(ret))

(define-string message "Strings are working!! 🥳"))
5 changes: 5 additions & 0 deletions src/backend/tests/c-lisp/casts.out
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ inf
7

3
0xffffff01
255
-1
127

1 change: 1 addition & 0 deletions src/backend/tests/c-lisp/string.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Strings are working!! 🥳
13 changes: 13 additions & 0 deletions src/backend/tests/c-lisp/string.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
;; ARGS: Identify_Me
(c-lisp
(define ((puts int) (s (ptr int8))))
(define ((strcmp int) (s1 (ptr int8)) (s2 (ptr int8))))

(define ((main void) (argc int) (argv (ptr (ptr int8))))
(declare success-msg (ptr int8))
(set success-msg "Strings are working!! 🥳")
(if (eq 0
(call strcmp
(load (ptradd argv 1))
"Identify_Me"))
(call puts success-msg))))
1 change: 1 addition & 0 deletions src/backend/tests/parser/string-2.json.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["brilisp",["define",[["puts","int"],["s",["ptr","int8"]]]],["define",[["main","void"]],["set",["tmp",["ptr","int8"]],["string-ref","message"]],["set",["tmp","int"],["call","puts","tmp"]],["ret"]],["define-string","message",["string","Strings are working!! 🥳"]]]
10 changes: 10 additions & 0 deletions src/backend/tests/parser/string-2.json.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
;; CMD: guile ../../utils/sexp-json.scm < {filename}
(brilisp
(define ((puts int) (s (ptr int8))))

(define ((main void))
(set (tmp (ptr int8)) (string-ref message))
(set (tmp int) (call puts tmp))
(ret))

(define-string message "Strings are working!! 🥳"))
7 changes: 7 additions & 0 deletions src/backend/tests/parser/string-2.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(brilisp
(define ((puts int) (s (ptr int8))))
(define ((main void))
(set (tmp (ptr int8)) (string-ref message))
(set (tmp int) (call puts tmp))
(ret))
(define-string message "Strings are working!! 🥳"))
10 changes: 10 additions & 0 deletions src/backend/tests/parser/string-2.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
;; CMD: guile ../../utils/sexp-json.scm < {filename} | guile ../../utils/json-sexp.scm
(brilisp
(define ((puts int) (s (ptr int8))))

(define ((main void))
(set (tmp (ptr int8)) (string-ref message))
(set (tmp int) (call puts tmp))
(ret))

(define-string message "Strings are working!! 🥳"))
1 change: 1 addition & 0 deletions src/backend/tests/parser/string.json.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["c-lisp",["define",[["puts","int"],["s",["ptr","int8"]]]],["define",[["strcmp","int"],["s1",["ptr","int8"]],["s2",["ptr","int8"]]]],["define",[["main","void"],["argc","int"],["argv",["ptr",["ptr","int8"]]]],["declare","success-msg",["ptr","int8"]],["set","success-msg",["string","Strings are working!! 🥳"]],["if",["eq",0,["call","strcmp",["load",["ptradd","argv",1]],["string","Identify_Me"]]],["call","puts","success-msg"]]]]
13 changes: 13 additions & 0 deletions src/backend/tests/parser/string.json.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
;; CMD: guile ../../utils/sexp-json.scm < {filename}
(c-lisp
(define ((puts int) (s (ptr int8))))
(define ((strcmp int) (s1 (ptr int8)) (s2 (ptr int8))))

(define ((main void) (argc int) (argv (ptr (ptr int8))))
(declare success-msg (ptr int8))
(set success-msg "Strings are working!! 🥳")
(if (eq 0
(call strcmp
(load (ptradd argv 1))
"Identify_Me"))
(call puts success-msg))))
11 changes: 11 additions & 0 deletions src/backend/tests/parser/string.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(c-lisp
(define ((puts int) (s (ptr int8))))
(define ((strcmp int) (s1 (ptr int8)) (s2 (ptr int8))))
(define ((main void) (argc int) (argv (ptr (ptr int8))))
(declare success-msg (ptr int8))
(set success-msg "Strings are working!! 🥳")
(if (eq 0
(call strcmp
(load (ptradd argv 1))
"Identify_Me"))
(call puts success-msg))))
13 changes: 13 additions & 0 deletions src/backend/tests/parser/string.sexp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
;; CMD: guile ../../utils/sexp-json.scm < {filename} | guile ../../utils/json-sexp.scm
(c-lisp
(define ((puts int) (s (ptr int8))))
(define ((strcmp int) (s1 (ptr int8)) (s2 (ptr int8))))

(define ((main void) (argc int) (argv (ptr (ptr int8))))
(declare success-msg (ptr int8))
(set success-msg "Strings are working!! 🥳")
(if (eq 0
(call strcmp
(load (ptradd argv 1))
"Identify_Me"))
(call puts success-msg))))
8 changes: 7 additions & 1 deletion src/backend/utils/json-sexp.scm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
(define (vec-list-recursive sexp)
(cond
((vector? sexp)
(map vec-list-recursive (vector->list sexp)))
(let ((op (vector-ref sexp 0)))
(if (and
(eq? (vector-length sexp) 2)
(string? op)
(string=? op "string"))
(vector-ref sexp 1)
(map vec-list-recursive (vector->list sexp)))))
((string? sexp) (string->symbol sexp))
(else sexp)))

Expand Down
8 changes: 4 additions & 4 deletions src/backend/utils/sexp-json.scm
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
(use-modules (json))

(define (list-vec-recursive sexp)
(if (list? sexp)
(list->vector (map list-vec-recursive sexp))
sexp
))
(cond
((list? sexp) (list->vector (map list-vec-recursive sexp)))
((string? sexp) (list->vector `(string ,sexp)))
(else sexp)))

(scm->json (list-vec-recursive (read)))
12 changes: 8 additions & 4 deletions src/backend/utils/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ def verify_shape(obj, template):
`None` can be used as a wildcard; so ["set", "a", ["add", 5, 1]] matches [str, str, None]
"""
if template is None:
# Wildcard pattern
return True
elif not isinstance(template, list):
elif isinstance(template, type):
# Verify object type
return isinstance(obj, template)
else:
elif isinstance(template, list):
# Recursively verify children
if not isinstance(obj, list):
return False

if len(obj) != len(template):
return False

for c in range(len(obj)):
if not verify_shape(obj[c], template[c]):
return False
else:
# Verify object value
return obj == template
return True

0 comments on commit c79daf7

Please sign in to comment.