Skip to content

Commit

Permalink
Add parse mode with stat
Browse files Browse the repository at this point in the history
  • Loading branch information
thaliaarchi committed Jun 26, 2023
1 parent 2394d5f commit 527ca1a
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 44 deletions.
6 changes: 4 additions & 2 deletions main.jq
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2021-2022 Thalia Archibald
# Copyright (c) 2021-2023 Thalia Archibald
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
Expand All @@ -17,9 +17,11 @@ $src | parse |
.eof = $no_prompt == "true" |
.check_clean = $check_clean == "true" |
.check_retrieve = $check_retrieve == "true" |
.filename = $filename |
if $mode == "run" then interpret
elif $mode == "debug" then debug
elif $mode == "disasm" then disasm_pc
elif $mode == "parse" then stat
else "\($mode|tojson) is not a valid mode\n" | halt_error(2)
end |
select(type == "string")
if $mode != "parse" then select(type == "string") else . end
92 changes: 61 additions & 31 deletions ws.jq
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2021-2022 Thalia Archibald
# Copyright (c) 2021-2023 Thalia Archibald
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
Expand All @@ -15,30 +15,32 @@ def cyan: color(36); def bright_cyan: color(96);
def white: color(37); def bright_white: color(97);

def inst_str:
if .arg != null then "\(.typ) \(.arg)" else .typ end;
if .arg != null then "\(.opcode) \(.arg)" else .opcode end;
def inst_asm:
if .typ == "label" then "\(.arg):"
if .opcode == "label" then "\(.arg):"
else " \(inst_str)" end;
def inst_asm_pc($pc; $breaks; $width):
if .pc == $pc then "\(.pc)#"|yellow
elif .pc|tostring | in($breaks) then
if $breaks[.pc|tostring] then "\(.pc)*"|red else "\(.pc)*" end
else "\(.pc)-" end +
if .typ == null then ""
if .opcode == null then ""
else
([$width - (.pc|tostring|length), 0] | max + 1) as $width |
" " * $width + inst_asm
end + "\n";

def inst_line($pos):
def inst_pos($offset):
.lines |
bsearch($pos) as $i |
bsearch($offset) as $i |
(if $i < 0 then -(2+$i) else $i end) as $i |
"\($i+1):\($pos-.[$i]+1)";
{line: ($i+1), col: ($offset-.[$i]+1)};
def inst_pos_str($offset):
inst_pos($offset) | "\(.line):\(.col)";

def prog_with_eof:
if .i != null and .i < (.src|length) then .prog
else .prog + [{pos:.src|length, pc:.prog|length}] end;
else .prog + [{offset:.src|length, pc:.prog|length}] end;

def disasm:
[.prog[] | inst_asm + "\n"] | join("");
Expand Down Expand Up @@ -100,8 +102,8 @@ def inst_error($msg; $inst; $pc):
($pc // .pc0 // .prog|length) as $pc |
($inst // .prog[$pc]) as $inst |
($msg|prefix_error)
+ if $inst.pos != null then
" at \(inst_line($inst.pos)) (offset \($inst.pos))" else "" end
+ if $inst.offset != null then
" at \(inst_pos_str($inst.offset)) (offset \($inst.offset))" else "" end
+ if $inst != null then ": \($inst | inst_str)" else "" end + "\n"
+ if .prog|length > 0 then "\n" + trace($pc; 4) else "" end
+ if .pc != null then "\n" + dump_state else "" end |
Expand All @@ -113,16 +115,20 @@ def assert($cond; msg; inst):
if $cond then . else inst_error(msg; inst) end;
def assert($cond; msg): assert($cond; msg; null);

def S: 32;
def T: 9;
def L: 10;

def match_char(s; t; l; eof):
.src[.i] as $ch | .i+=1 |
if $ch == 32 then .tok += "[Space]" | s
elif $ch == 9 then .tok += "[Tab]" | t
elif $ch == 10 then .tok += "[LF]" | .lines += [.i] | l
if $ch == S then .tok += "[Space]" | s
elif $ch == T then .tok += "[Tab]" | t
elif $ch == L then .tok += "[LF]" | .lines += [.i] | l
elif .i >= (.src|length) then eof
else match_char(s; t; l; eof) end;
def match_char(s; t; l):
match_char(s; t; l;
inst_error("unexpected EOF"; {typ:(.tok+"[EOF]"), pos:(.i-1)}));
inst_error("unexpected EOF"; {opcode:(.tok+"[EOF]"), offset:(.i-1)}));

def parse_inst:
def parse_num:
Expand All @@ -147,19 +153,19 @@ def parse_inst:
elif length > 1 and .[0] == 0 then "%b\(join(""))"
else "%\($n)" end;

def inst($typ): .prog += [{typ:$typ, pos, pc:.prog|length}];
def inst_num($typ):
def inst($opcode): .prog += [{opcode:$opcode, offset, pc:.prog|length}];
def inst_num($opcode):
.n = 0 | match_char(parse_num; parse_num | .n*=-1; .) |
if .n == 0 then .n = 0 else . end | # Normalize -0
.prog += [{typ:$typ, arg:.n, pos, pc:.prog|length}] |
.prog += [{opcode:$opcode, arg:.n, offset, pc:.prog|length}] |
del(.n);
def inst_lbl($typ):
def inst_lbl($opcode):
.n = 0 | .l = [] | parse_lbl |
.prog += [{typ:$typ, arg:lbl_str, pos, pc:.prog|length}] |
.prog += [{opcode:$opcode, arg:lbl_str, offset, pc:.prog|length}] |
del(.n, .l);
def inst_err: inst_error("unrecognized instruction"; {typ:.tok, pos});
def inst_err: inst_error("unrecognized instruction"; {opcode:.tok, offset});

.pos = .i | .tok = "" |
.offset = .i | .tok = "" |
match_char(
# Stack
match_char(
Expand Down Expand Up @@ -219,7 +225,7 @@ def parse_inst:

def label_map:
. as $state |
reduce (.prog[] | select(.typ == "label")) as $inst
reduce (.prog[] | select(.opcode == "label")) as $inst
({}; . as $labels |($inst.arg|tostring) as $lbl |
$state | assert($labels[$lbl] == null; "label redefined"; $inst) |
$labels | .[$lbl] = $inst.pc);
Expand All @@ -228,17 +234,41 @@ def parse:
{
src: explode, # program source
i: 0, # read offset
pos: 0, # instruction start offset
offset: 0, # instruction start offset
tok: "", # current token
lines: [0], # offsets of lines
prog: [], # instructions
} |
def _parse:
if .i < (.src|length) then parse_inst | _parse else . end;
_parse |
del(.i, .pos, .tok) |
del(.i, .offset, .tok) |
.labels = label_map;

def stat:
. as $state |
.prog |= map(.offset as $offset | . * ($state | inst_pos($offset))) |
(.prog |
group_by(.opcode) |
map({key:.[0].opcode, value:length}) |
sort_by([-.value, .key]) | from_entries
) as $inst_counts |
(.src | {
space: (map(select(. == S)) | length),
tab: (map(select(. == T)) | length),
lf: (map(select(. == L)) | length),
}) as $token_counts |
(if $inst_counts.copy != null or $inst_counts.slide != null
then "0.3" else "0.2" end) as $spec_version |
{
filename: $filename,
program: .prog,
labels,
spec_version: $spec_version,
inst_counts: $inst_counts,
token_counts: $token_counts,
};

def floor_div($x; $y):
($x % $y) as $r |
(($x - $r) / $y) as $q |
Expand Down Expand Up @@ -306,7 +336,7 @@ def interpret_step:
store(top; $n) | pop;

assert(.pc < (.prog|length); "interpreter stopped") |
.prog[.pc] as $inst | $inst as {typ:$t, arg:$n} |
.prog[.pc] as $inst | $inst as {opcode:$t, arg:$n} |
.pc0 = .pc | .pc += 1 |
if $t == "push" then push($n)
elif $t == "dup" then push(top)
Expand Down Expand Up @@ -338,7 +368,7 @@ def interpret_step_debug:
def print_exit_status:
if type != "object" or .pc < (.prog|length) then empty
else
if .prog[.pc0].typ == "end"
if .prog[.pc0].opcode == "end"
then "[program exited cleanly]\n"|green
else "[program exited implicitly]\n"|red end
end;
Expand Down Expand Up @@ -380,19 +410,19 @@ def interpret_next:
elif .moved and .breaks[.pc|tostring] then
("[stopped at breakpoint]\n"|red), .
else
.prog[.pc0].typ as $typ |
(if $typ == "call" then $depth+1
elif $typ == "ret" then $depth-1
.prog[.pc0].opcode as $opcode |
(if $opcode == "call" then $depth+1
elif $opcode == "ret" then $depth-1
else $depth end) as $depth |
if $depth > 0 then _next($depth) else . end
end;
_next(0);

def check_clean_exit:
if type == "object" and .check_clean then
if .prog[.pc0].typ != "end" and (.s|length != 0) then
if .prog[.pc0].opcode != "end" and (.s|length != 0) then
inst_error("exited implicitly with non-empty stack")
elif .prog[.pc0].typ != "end" then
elif .prog[.pc0].opcode != "end" then
inst_error("exited implicitly")
elif .s|length != 0 then
inst_error("exited with non-empty stack")
Expand Down
32 changes: 21 additions & 11 deletions wsjq
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#!/bin/bash
# Copyright (c) 2021-2022 Thalia Archibald
# Copyright (c) 2021-2023 Thalia Archibald
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

set -euo pipefail

SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
SCRIPT_NAME="${0##*/}"

USAGE="usage: ${0##*/} [<mode>] [<options>] <file>
USAGE="Usage: $SCRIPT_NAME [<mode>] [<options>] <file>
Modes:
run -- Run the program (default)
debug -- Run the program in the debugger
disasm -- Disassemble the program
parse -- Dump the parsed program and stats
Options:
-i, --in-file=<in_file>
Expand Down Expand Up @@ -41,12 +45,12 @@ Options:
"

usage() {
echo -n "$USAGE" 2>&2
echo -n "$USAGE" >&2
exit 2
}

mode=run
if [ "$1" = run ] || [ "$1" = debug ] || [ "$1" = disasm ]; then
if [[ $# != 0 && ($1 = run || $1 = debug || $1 = disasm || $1 = parse) ]]; then
mode="$1"
shift
fi
Expand All @@ -57,7 +61,7 @@ check_clean=false
check_retrieve=false
jq=jq

while getopts ':hi:e:crn-:' optchar; do
while getopts ':hi:e:ncr-:' optchar; do
case "$optchar" in
-)
case "$OPTARG" in
Expand All @@ -84,25 +88,31 @@ while getopts ':hi:e:crn-:' optchar; do
done
shift $((OPTIND-1))

if [ $# -ne 1 ]; then
if [[ $# -ne 1 ]]; then
usage
fi

file="$1"
if [ ! -f "$file" ]; then
echo "${0##*/}: Program file not found: $file" >&2; exit 1
if [[ ! -f $file ]]; then
echo "$SCRIPT_NAME: Program file not found: $file" >&2; exit 1
fi
if [ ! -f "$in_file" ] && [ "$in_file" != /dev/null ]; then
echo "${0##*/}: Input file not found: $in_file" >&2; exit 1
if [[ ! -f $in_file && $in_file != /dev/null ]]; then
echo "$SCRIPT_NAME: Input file not found: $in_file" >&2; exit 1
fi

jq="${jq/#\~/$HOME}"
$jq -njMR \
options=-njMR
if [[ $mode = parse ]]; then
options=-nR
fi

$jq $options \
--arg mode "$mode" \
--arg on_eof "$on_eof" \
--arg no_prompt "$no_prompt" \
--arg check_clean "$check_clean" \
--arg check_retrieve "$check_retrieve" \
--arg filename "$file" \
--rawfile src "$file" \
--rawfile in "$in_file" \
-L "$SCRIPT_DIR" \
Expand Down

0 comments on commit 527ca1a

Please sign in to comment.