~james/remy

21d77cf288325dcfb456295637061e22fb3d28e9 — james verified a month ago 7d99400
feat(frontend): parse bounded hexadecimal word literals
4 files changed, 61 insertions(+), 3 deletions(-)

M Makefile
M src/Examples.thy
M src/Frontend.thy
A tests/cli.py
M Makefile => Makefile +1 -0
@@ 13,6 13,7 @@ all: .build/ready

check: all
	python3 tests/native.py
	python3 tests/cli.py

payload: all
	@mkdir -p .build/payload

M src/Examples.thy => src/Examples.thy +9 -0
@@ 90,6 90,15 @@ lemma trailing_rejected: "prepare ''(+ x y) 1'' = None"
lemma literal_overflow_rejected: "prepare ''18446744073709551616'' = None"
  by eval

lemma hexadecimal_boundaries:
  "number ''0xffffffffffffffff'' = Some (2 ^ 64 - 1) \<and>
    number ''0x10000000000000000'' = None"
  by eval

lemma hexadecimal_execution:
  "reference ''(+ x 0XfF)'' ''0x1'' ''0'' ''0'' = (True,''256'' @ newline)"
  by eval

lemma incomplete_rejected: "prepare ''(if x y)'' = None"
  by eval


M src/Frontend.thy => src/Frontend.thy +17 -3
@@ 13,11 13,25 @@ fun lex :: "string \<Rightarrow> string \<Rightarrow> string list \<Rightarrow> 
    else if c = CHR ''('' \<or> c = CHR '')'' then lex cs [] ([c] # finish word out)
    else lex cs (c # word) out)"

definition number :: "string \<Rightarrow> nat option" where
  "number s = (if s = [] \<or> \<not> list_all (\<lambda>c. 48 \<le> nat_of_char c \<and> nat_of_char c < 58) s
    then None else let n = foldl (\<lambda>n c. min (2 ^ 64) (10 * n + nat_of_char c - 48)) 0 s in
fun digit :: "char \<Rightarrow> nat" where
  "digit c = (let n = nat_of_char c in
    if 48 \<le> n \<and> n < 58 then n - 48 else
    if 65 \<le> n \<and> n < 71 then n - 55 else
    if 97 \<le> n \<and> n < 103 then n - 87 else 16)"

definition parse_digits :: "nat \<Rightarrow> string \<Rightarrow> nat option" where
  "parse_digits base s = (if s = [] \<or> \<not> list_all (\<lambda>c. digit c < base) s
    then None else let n = foldl (\<lambda>n c. min (2 ^ 64) (base * n + digit c)) 0 s in
    if n < 2 ^ 64 then Some n else None)"

definition number :: "string \<Rightarrow> nat option" where
  "number s = (case s of a # b # rest \<Rightarrow>
    if a = CHR ''0'' \<and> (b = CHR ''x'' \<or> b = CHR ''X'') then parse_digits 16 rest
    else parse_digits 10 s | _ \<Rightarrow> parse_digits 10 s)"

theorem number_bound: "number s = Some n \<Longrightarrow> n < 2 ^ 64"
  by (auto simp: number_def parse_digits_def Let_def split: list.splits if_splits)

definition valid_name :: "string \<Rightarrow> bool" where
  "valid_name s = (s \<noteq> [] \<and> list_all (\<lambda>c. (97 \<le> nat_of_char c \<and> nat_of_char c \<le> 122)) s
    \<and> s \<noteq> ''let'' \<and> s \<noteq> ''if'')"

A tests/cli.py => tests/cli.py +34 -0
@@ 0,0 1,34 @@
from pathlib import Path
import subprocess
import tempfile


ROOT = Path(__file__).resolve().parents[1]


def invoke(*args, ok=True, data=None, preexec_fn=None):
    result = subprocess.run([str(ROOT / '.build/remy'), *map(str, args)], input=data,
        capture_output=True, text=True, timeout=30, preexec_fn=preexec_fn)
    assert (result.returncode == 0) == ok, (args, result.stdout, result.stderr)
    return result.stdout if ok else result.stderr


def main():
    with tempfile.TemporaryDirectory() as directory:
        source, binary = Path(directory) / 'source', Path(directory) / 'binary'
        for text, expected in [('0x0',0),('0xff',255),('0X7F',127),('0xffffffffffffffff',(1 << 64)-1),
            ('(+ 0xffffffffffffffff 1)',0),('(< 0x8000000000000000 0xffffffffffffffff)',1)]:
            source.write_text(text)
            assert int(invoke('eval',source,'0','0','0')) == expected, text
        source.write_text('(+ x y)')
        assert int(invoke('eval',source,'0xFF','0x1','0x0')) == 256
        for text in ['0x','0x10000000000000000','0xfg','0xffextra','-0x1']:
            source.write_text(text)
            binary.write_bytes(b'preserve')
            invoke(source,binary,ok=False)
            assert binary.read_bytes() == b'preserve', text
    print('CLI literal and rejection checks passed')


if __name__ == '__main__':
    main()