Repository navigation
Expand file tree
/
Copy pathchip.go
More file actions
188 lines (171 loc) · 5.71 KB
/
Copy pathchip.go
File metadata and controls
188 lines (171 loc) · 5.71 KB
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Package chip embeds the chip toy language: it parses, type-checks, compiles,
// and runs chip source from Go.
//
// The command-line tool lives in cmd/chip; this package is the library entry
// point for embedding chip in other programs. The implementation lives under
// internal/ and is deliberately not part of the public API.
package chip
import (
"bytes"
"context"
"fmt"
"io"
"github.com/jackspirou/chip/internal/ast"
"github.com/jackspirou/chip/internal/check"
"github.com/jackspirou/chip/internal/code"
"github.com/jackspirou/chip/internal/compiler"
"github.com/jackspirou/chip/internal/format"
"github.com/jackspirou/chip/internal/lint"
"github.com/jackspirou/chip/internal/parser"
"github.com/jackspirou/chip/internal/stream"
"github.com/jackspirou/chip/internal/vm"
)
// Diagnostic is a positioned message produced while processing chip source.
type Diagnostic struct {
Line int
Col int
Msg string
}
// String formats the diagnostic as "line:col: message".
func (d Diagnostic) String() string {
return fmt.Sprintf("%d:%d: %s", d.Line, d.Col, d.Msg)
}
// Error reports one or more diagnostics from compiling or running chip source.
type Error struct {
Diagnostics []Diagnostic
}
// Error implements the error interface.
func (e *Error) Error() string {
switch len(e.Diagnostics) {
case 0:
return "chip: unknown error"
case 1:
return e.Diagnostics[0].String()
default:
return fmt.Sprintf("%s (and %d more)", e.Diagnostics[0], len(e.Diagnostics)-1)
}
}
// Program is a compiled chip program, ready to run.
type Program struct {
prog *code.Program
}
// Compile parses, type-checks, and compiles chip source into a Program. On
// failure it returns an *Error carrying positioned diagnostics.
func Compile(src []byte) (*Program, error) {
file, err := parse(src)
if err != nil {
return nil, asError(err)
}
info, err := check.Check(file)
if err != nil {
return nil, asError(err)
}
prog, err := compiler.Compile(file, info)
if err != nil {
return nil, asError(err)
}
return &Program{prog: prog}, nil
}
// Run executes the program, writing its output to out.
func (p *Program) Run(out io.Writer) error {
if err := vm.Run(p.prog, out); err != nil {
return asError(err)
}
return nil
}
// Run streams and executes chip source, writing program output to out. Unlike
// Compile, it runs the program as a demand-driven stream: there is no
// whole-program gate, and top-level statements run as they arrive. On a parse,
// type, or runtime error it returns an *Error carrying positioned diagnostics.
func Run(src []byte, out io.Writer) error {
return asError(stream.Run(bytes.NewReader(src), out))
}
// Limits bounds a run's resource use for RunWith (plan §4.1). A zero field
// imposes no limit on that dimension; a zero Depth uses chip's default
// call-depth limit. The caps are enforced cooperatively, so Steps is
// deterministic for a given program and input.
type Limits struct {
Steps int64 // max interpreter steps; 0 = unlimited
OutBytes int64 // max bytes written to w; 0 = unlimited
Depth int // max call depth; 0 = default
}
// RunWith streams and executes chip source from r like Run while enforcing
// resource caps: ctx cancellation imposes a wall-clock timeout (a cancelled ctx
// stops the run and is reported as a timeout), and lim bounds interpreter steps,
// output bytes, and call depth. Program output is written to w; imports resolve
// against the current directory. On a parse, type, runtime, or resource-cap
// error it returns an *Error carrying positioned diagnostics.
func RunWith(ctx context.Context, r io.Reader, w io.Writer, lim Limits) error {
return asError(stream.RunWithLimits(ctx, r, w, stream.DirLoader("."), stream.Limits{
Steps: lim.Steps,
OutBytes: lim.OutBytes,
Depth: lim.Depth,
}))
}
// Format returns src rewritten in canonical form. Like gofmt, it only requires
// the source to parse; it does not type-check.
func Format(src []byte) ([]byte, error) {
out, err := format.Source(src)
if err != nil {
return nil, asError(err)
}
return out, nil
}
// Lint type-checks src and returns any style and correctness issues.
func Lint(src []byte) ([]Diagnostic, error) {
file, err := parse(src)
if err != nil {
return nil, asError(err)
}
info, err := check.Check(file)
if err != nil {
return nil, asError(err)
}
return diagnostics(lint.Lint(file, info)), nil
}
func parse(src []byte) (*ast.File, error) {
p, err := parser.New(bytes.NewReader(src))
if err != nil {
return nil, err
}
return p.Parse()
}
// asError converts a known internal positioned error into a public *Error, or
// returns err unchanged when it carries no diagnostics.
func asError(err error) error {
if err == nil {
return nil
}
if ds := diagnostics(err); len(ds) > 0 {
return &Error{Diagnostics: ds}
}
return err
}
// diagnostics extracts positioned diagnostics from any known internal error or
// issue list.
func diagnostics(v any) []Diagnostic {
var ds []Diagnostic
switch e := v.(type) {
case parser.ErrorList:
for _, d := range e {
ds = append(ds, Diagnostic{Line: d.Pos.Line, Col: d.Pos.Column, Msg: d.Msg})
}
case check.ErrorList:
for _, d := range e {
ds = append(ds, Diagnostic{Line: d.Pos.Line, Col: d.Pos.Column, Msg: d.Msg})
}
case lint.IssueList:
for _, d := range e {
ds = append(ds, Diagnostic{Line: d.Pos.Line, Col: d.Pos.Column, Msg: d.Msg})
}
case vm.RuntimeError:
ds = append(ds, Diagnostic{Line: e.Pos.Line, Col: e.Pos.Column, Msg: e.Msg})
case stream.TypeError:
ds = append(ds, Diagnostic{Line: e.Pos.Line, Col: e.Pos.Column, Msg: e.Msg})
case stream.RuntimeError:
ds = append(ds, Diagnostic{Line: e.Pos.Line, Col: e.Pos.Column, Msg: e.Msg})
case stream.ResourceError:
ds = append(ds, Diagnostic{Line: e.Pos.Line, Col: e.Pos.Column, Msg: e.Msg})
}
return ds
}