codecin 5.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codecin/__init__.py +27 -0
- codecin/aot.py +229 -0
- codecin/assembler.py +548 -0
- codecin/cache.py +71 -0
- codecin/cin.py +2558 -0
- codecin/cli.py +282 -0
- codecin/config.py +58 -0
- codecin/console.py +183 -0
- codecin/cpu.py +1654 -0
- codecin/crom.py +222 -0
- codecin/debugger.py +659 -0
- codecin/disasm.py +86 -0
- codecin/errors.py +58 -0
- codecin/isa.py +457 -0
- codecin/jit.py +270 -0
- codecin/libcodecin_native.so +0 -0
- codecin/logger.py +145 -0
- codecin/memory.py +348 -0
- codecin/native/aot/aot.go +70 -0
- codecin/native/aot/build.go +219 -0
- codecin/native/aot/stub_main.go.txt +22 -0
- codecin/native/build.ps1 +33 -0
- codecin/native/build.sh +48 -0
- codecin/native/cmd/codecin/main.go +287 -0
- codecin/native/compiler/codegen.go +1938 -0
- codecin/native/compiler/compile_test.go +164 -0
- codecin/native/compiler/parser.go +1071 -0
- codecin/native/compiler/syscalls.go +86 -0
- codecin/native/compiler/tokenizer.go +336 -0
- codecin/native/compiler/types.go +213 -0
- codecin/native/engine/audio.go +152 -0
- codecin/native/engine/audio_other.go +46 -0
- codecin/native/engine/audio_windows.go +43 -0
- codecin/native/engine/canvas.go +271 -0
- codecin/native/engine/crom.go +86 -0
- codecin/native/engine/encode.go +93 -0
- codecin/native/engine/engine_test.go +198 -0
- codecin/native/engine/isa_gen.go +445 -0
- codecin/native/engine/system.go +190 -0
- codecin/native/engine/termux.go +113 -0
- codecin/native/engine/version_gen.go +7 -0
- codecin/native/engine/vm.go +1157 -0
- codecin/native/go.mod +3 -0
- codecin/native/ir/ir.go +48 -0
- codecin/native/main.go +225 -0
- codecin/native.py +336 -0
- codecin/registers.py +130 -0
- codecin/stats.py +235 -0
- codecin-5.4.2.dist-info/METADATA +985 -0
- codecin-5.4.2.dist-info/RECORD +54 -0
- codecin-5.4.2.dist-info/WHEEL +5 -0
- codecin-5.4.2.dist-info/entry_points.txt +2 -0
- codecin-5.4.2.dist-info/licenses/LICENSE +7 -0
- codecin-5.4.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
package engine
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/binary"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
"net/http"
|
|
8
|
+
"os"
|
|
9
|
+
"os/exec"
|
|
10
|
+
"strings"
|
|
11
|
+
"time"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// 联网音频: 下载 (http/https) 或本地文件 → 解析 WAV → 平台播放。
|
|
15
|
+
// 支持格式: WAV (PCM)。MP3/OGG 需外部解码器, 本实现不包含。
|
|
16
|
+
//
|
|
17
|
+
// 平台播放后端由构建约束拆分:
|
|
18
|
+
// audio_windows.go (//go:build windows) —— winmm PlaySoundW
|
|
19
|
+
// audio_other.go (//go:build !windows) —— afplay / aplay / paplay / ffplay
|
|
20
|
+
|
|
21
|
+
// 宿主全局音频状态 (同一时刻一个音频流)。
|
|
22
|
+
var (
|
|
23
|
+
audioActive bool
|
|
24
|
+
audioDur time.Duration
|
|
25
|
+
audioEnd time.Time
|
|
26
|
+
audioTemp string // 当前播放的临时 WAV 文件路径 (异步播放时保留)
|
|
27
|
+
audioCmd *exec.Cmd
|
|
28
|
+
audioStarted time.Time
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// MaxResourceBytes 是 FetchResource 的下载/读取上限 (音频等宿主资源)。
|
|
32
|
+
const MaxResourceBytes = 64 << 20 // 64 MiB
|
|
33
|
+
|
|
34
|
+
// FetchResource 下载 URL 或读取本地文件, 返回原始字节。
|
|
35
|
+
func FetchResource(loc string) ([]byte, error) {
|
|
36
|
+
if strings.HasPrefix(loc, "http://") || strings.HasPrefix(loc, "https://") {
|
|
37
|
+
client := &http.Client{Timeout: 30 * time.Second}
|
|
38
|
+
resp, err := client.Get(loc)
|
|
39
|
+
if err != nil {
|
|
40
|
+
return nil, err
|
|
41
|
+
}
|
|
42
|
+
defer resp.Body.Close()
|
|
43
|
+
if resp.StatusCode != http.StatusOK {
|
|
44
|
+
return nil, fmt.Errorf("http status %d", resp.StatusCode)
|
|
45
|
+
}
|
|
46
|
+
// 有上限读取: Content-Length 与实际读取量双重限制,
|
|
47
|
+
// 防止恶意/异常 URL 用无限流把宿主 OOM。
|
|
48
|
+
if resp.ContentLength > MaxResourceBytes {
|
|
49
|
+
return nil, fmt.Errorf("resource too large: %d bytes", resp.ContentLength)
|
|
50
|
+
}
|
|
51
|
+
data, err := io.ReadAll(io.LimitReader(resp.Body, MaxResourceBytes+1))
|
|
52
|
+
if err != nil {
|
|
53
|
+
return nil, err
|
|
54
|
+
}
|
|
55
|
+
if len(data) > MaxResourceBytes {
|
|
56
|
+
return nil, fmt.Errorf("resource too large: >%d bytes", MaxResourceBytes)
|
|
57
|
+
}
|
|
58
|
+
return data, nil
|
|
59
|
+
}
|
|
60
|
+
fi, err := os.Stat(loc)
|
|
61
|
+
if err == nil && fi.Size() > MaxResourceBytes {
|
|
62
|
+
return nil, fmt.Errorf("resource too large: %d bytes", fi.Size())
|
|
63
|
+
}
|
|
64
|
+
return os.ReadFile(loc)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// WavDuration 解析 WAV 头并返回时长 (0 表示无法识别)。
|
|
68
|
+
func WavDuration(data []byte) time.Duration {
|
|
69
|
+
if len(data) < 44 || string(data[0:4]) != "RIFF" || string(data[8:12]) != "WAVE" {
|
|
70
|
+
return 0
|
|
71
|
+
}
|
|
72
|
+
byteRate := binary.LittleEndian.Uint32(data[28:32])
|
|
73
|
+
dataSize := binary.LittleEndian.Uint32(data[40:44])
|
|
74
|
+
if byteRate == 0 {
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
return time.Duration(float64(dataSize) / float64(byteRate) * float64(time.Second))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// isWav 判断是否为 WAV (RIFF/WAVE)。
|
|
81
|
+
func isWav(data []byte) bool {
|
|
82
|
+
return len(data) >= 12 && string(data[0:4]) == "RIFF" && string(data[8:12]) == "WAVE"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// writeTempWav 将 WAV 字节写入临时文件并记录路径。
|
|
86
|
+
func writeTempWav(data []byte) (string, error) {
|
|
87
|
+
f, err := os.CreateTemp("", "codecin_*.wav")
|
|
88
|
+
if err != nil {
|
|
89
|
+
return "", err
|
|
90
|
+
}
|
|
91
|
+
if _, err := f.Write(data); err != nil {
|
|
92
|
+
_ = f.Close()
|
|
93
|
+
_ = os.Remove(f.Name())
|
|
94
|
+
return "", err
|
|
95
|
+
}
|
|
96
|
+
if err := f.Close(); err != nil {
|
|
97
|
+
_ = os.Remove(f.Name())
|
|
98
|
+
return "", err
|
|
99
|
+
}
|
|
100
|
+
audioTemp = f.Name()
|
|
101
|
+
return audioTemp, nil
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// cleanupTempWav 删除临时 WAV 文件。
|
|
105
|
+
func cleanupTempWav() {
|
|
106
|
+
if audioTemp != "" {
|
|
107
|
+
_ = os.Remove(audioTemp)
|
|
108
|
+
audioTemp = ""
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (vm *vmState) audioPlay(loc string) uint64 {
|
|
113
|
+
data, err := FetchResource(loc)
|
|
114
|
+
if err != nil {
|
|
115
|
+
return mask64 // -1
|
|
116
|
+
}
|
|
117
|
+
if !isWav(data) {
|
|
118
|
+
return mask64 // 不支持的格式
|
|
119
|
+
}
|
|
120
|
+
if err := playPlatform(data); err != nil {
|
|
121
|
+
return mask64
|
|
122
|
+
}
|
|
123
|
+
audioActive = true
|
|
124
|
+
audioDur = WavDuration(data)
|
|
125
|
+
audioStarted = time.Now()
|
|
126
|
+
audioEnd = audioStarted.Add(audioDur)
|
|
127
|
+
return 0
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func (vm *vmState) audioStop() {
|
|
131
|
+
audioActive = false
|
|
132
|
+
stopPlatform()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func (vm *vmState) audioVolume(level uint64) {
|
|
136
|
+
// 音量控制依赖 OS 混音器, 本实现忽略 (保留参数以便后续扩展)。
|
|
137
|
+
_ = level
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func (vm *vmState) audioWait() {
|
|
141
|
+
if !audioActive {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if audioDur <= 0 {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
remain := time.Until(audioEnd)
|
|
148
|
+
if remain > 0 {
|
|
149
|
+
time.Sleep(remain)
|
|
150
|
+
}
|
|
151
|
+
audioActive = false
|
|
152
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//go:build !windows
|
|
2
|
+
|
|
3
|
+
package engine
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"fmt"
|
|
7
|
+
"os/exec"
|
|
8
|
+
"runtime"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// Unix 音频后端 (Linux / macOS / Termux / BSD): 调用系统播放器。
|
|
12
|
+
// macOS: afplay
|
|
13
|
+
// Linux/Termux: aplay / paplay / ffplay (取第一个可用者)
|
|
14
|
+
|
|
15
|
+
func playPlatform(data []byte) error {
|
|
16
|
+
path, err := writeTempWav(data)
|
|
17
|
+
if err != nil {
|
|
18
|
+
return err
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if runtime.GOOS == "darwin" {
|
|
22
|
+
audioCmd = exec.Command("afplay", path)
|
|
23
|
+
return audioCmd.Start()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for _, p := range []string{"aplay", "paplay", "ffplay"} {
|
|
27
|
+
if _, err := exec.LookPath(p); err != nil {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
if p == "ffplay" {
|
|
31
|
+
audioCmd = exec.Command(p, "-nodisp", "-autoexit", "-loglevel", "quiet", path)
|
|
32
|
+
} else {
|
|
33
|
+
audioCmd = exec.Command(p, path)
|
|
34
|
+
}
|
|
35
|
+
return audioCmd.Start()
|
|
36
|
+
}
|
|
37
|
+
return fmt.Errorf("no audio player found (aplay/paplay/ffplay)")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func stopPlatform() {
|
|
41
|
+
if audioCmd != nil && audioCmd.Process != nil {
|
|
42
|
+
_ = audioCmd.Process.Kill()
|
|
43
|
+
}
|
|
44
|
+
audioCmd = nil
|
|
45
|
+
cleanupTempWav()
|
|
46
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//go:build windows
|
|
2
|
+
|
|
3
|
+
package engine
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"fmt"
|
|
7
|
+
"syscall"
|
|
8
|
+
"unsafe"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// Windows 音频后端: winmm.dll PlaySoundW (异步)。
|
|
12
|
+
|
|
13
|
+
var (
|
|
14
|
+
winmm = syscall.NewLazyDLL("winmm.dll")
|
|
15
|
+
playSoundW = winmm.NewProc("PlaySoundW")
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const (
|
|
19
|
+
sndAsync = 0x0001
|
|
20
|
+
sndPurge = 0x0040
|
|
21
|
+
sndFilename = 0x00020000
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
func playPlatform(data []byte) error {
|
|
25
|
+
path, err := writeTempWav(data)
|
|
26
|
+
if err != nil {
|
|
27
|
+
return err
|
|
28
|
+
}
|
|
29
|
+
p, err := syscall.UTF16PtrFromString(path)
|
|
30
|
+
if err != nil {
|
|
31
|
+
return err
|
|
32
|
+
}
|
|
33
|
+
r, _, _ := playSoundW.Call(uintptr(unsafe.Pointer(p)), 0, sndFilename|sndAsync)
|
|
34
|
+
if r == 0 {
|
|
35
|
+
return fmt.Errorf("PlaySoundW failed")
|
|
36
|
+
}
|
|
37
|
+
return nil
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func stopPlatform() {
|
|
41
|
+
playSoundW.Call(0, 0, sndPurge)
|
|
42
|
+
cleanupTempWav()
|
|
43
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
package engine
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"image"
|
|
6
|
+
"image/color"
|
|
7
|
+
"image/png"
|
|
8
|
+
"os"
|
|
9
|
+
"os/exec"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"runtime"
|
|
12
|
+
"strings"
|
|
13
|
+
"time"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// 2D 绘图画布 (Go 标准库 image/png 实现): 新建画布 → 着色 → 绘制形状/文本 → 导出 PNG。
|
|
17
|
+
// 宿主全局状态: 同一时刻一个「当前画布」与「当前颜色」。
|
|
18
|
+
|
|
19
|
+
var (
|
|
20
|
+
curCanvas *image.RGBA
|
|
21
|
+
curColor = color.RGBA{R: 0, G: 0, B: 0, A: 255} // 默认黑色画笔
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
func clampCoord(v uint64) int {
|
|
25
|
+
if v > 100000 {
|
|
26
|
+
return 100000
|
|
27
|
+
}
|
|
28
|
+
return int(v)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func (vm *vmState) canvasNew(w, h uint64) {
|
|
32
|
+
cw, ch := clampCoord(w), clampCoord(h)
|
|
33
|
+
if cw < 1 {
|
|
34
|
+
cw = 1
|
|
35
|
+
}
|
|
36
|
+
if ch < 1 {
|
|
37
|
+
ch = 1
|
|
38
|
+
}
|
|
39
|
+
curCanvas = image.NewRGBA(image.Rect(0, 0, cw, ch))
|
|
40
|
+
// 白色背景
|
|
41
|
+
for i := range curCanvas.Pix {
|
|
42
|
+
curCanvas.Pix[i] = 255
|
|
43
|
+
}
|
|
44
|
+
curColor = color.RGBA{R: 0, G: 0, B: 0, A: 255}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func (vm *vmState) canvasSetColor(rgb uint64) {
|
|
48
|
+
curColor = color.RGBA{
|
|
49
|
+
R: uint8((rgb >> 16) & 0xFF),
|
|
50
|
+
G: uint8((rgb >> 8) & 0xFF),
|
|
51
|
+
B: uint8(rgb & 0xFF),
|
|
52
|
+
A: 255,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func (vm *vmState) canvasRect(x, y, w, h uint64) {
|
|
57
|
+
if curCanvas == nil {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
x0, y0 := clampCoord(x), clampCoord(y)
|
|
61
|
+
x1, y1 := x0+clampCoord(w), y0+clampCoord(h)
|
|
62
|
+
b := curCanvas.Bounds()
|
|
63
|
+
if x0 < b.Min.X {
|
|
64
|
+
x0 = b.Min.X
|
|
65
|
+
}
|
|
66
|
+
if y0 < b.Min.Y {
|
|
67
|
+
y0 = b.Min.Y
|
|
68
|
+
}
|
|
69
|
+
if x1 > b.Max.X {
|
|
70
|
+
x1 = b.Max.X
|
|
71
|
+
}
|
|
72
|
+
if y1 > b.Max.Y {
|
|
73
|
+
y1 = b.Max.Y
|
|
74
|
+
}
|
|
75
|
+
for py := y0; py < y1; py++ {
|
|
76
|
+
for px := x0; px < x1; px++ {
|
|
77
|
+
curCanvas.SetRGBA(px, py, curColor)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func (vm *vmState) canvasCircle(cx, cy, r uint64) {
|
|
83
|
+
if curCanvas == nil {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
ccx, ccy := clampCoord(cx), clampCoord(cy)
|
|
87
|
+
rr := clampCoord(r)
|
|
88
|
+
rr2 := rr * rr
|
|
89
|
+
b := curCanvas.Bounds()
|
|
90
|
+
for py := ccy - rr; py <= ccy+rr; py++ {
|
|
91
|
+
for px := ccx - rr; px <= ccx+rr; px++ {
|
|
92
|
+
dx := px - ccx
|
|
93
|
+
dy := py - ccy
|
|
94
|
+
if dx*dx+dy*dy <= rr2 {
|
|
95
|
+
if px >= b.Min.X && px < b.Max.X && py >= b.Min.Y && py < b.Max.Y {
|
|
96
|
+
curCanvas.SetRGBA(px, py, curColor)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func (vm *vmState) canvasLine(x0, y0, x1, y1 uint64) {
|
|
104
|
+
if curCanvas == nil {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
ax, ay := clampCoord(x0), clampCoord(y0)
|
|
108
|
+
bx, by := clampCoord(x1), clampCoord(y1)
|
|
109
|
+
b := curCanvas.Bounds()
|
|
110
|
+
// Bresenham
|
|
111
|
+
dx := bx - ax
|
|
112
|
+
if dx < 0 {
|
|
113
|
+
dx = -dx
|
|
114
|
+
}
|
|
115
|
+
sx := 1
|
|
116
|
+
if ax > bx {
|
|
117
|
+
sx = -1
|
|
118
|
+
}
|
|
119
|
+
dy := by - ay
|
|
120
|
+
if dy < 0 {
|
|
121
|
+
dy = -dy
|
|
122
|
+
}
|
|
123
|
+
sy := 1
|
|
124
|
+
if ay > by {
|
|
125
|
+
sy = -1
|
|
126
|
+
}
|
|
127
|
+
err := dx - dy
|
|
128
|
+
for {
|
|
129
|
+
if ax >= b.Min.X && ax < b.Max.X && ay >= b.Min.Y && ay < b.Max.Y {
|
|
130
|
+
curCanvas.SetRGBA(ax, ay, curColor)
|
|
131
|
+
}
|
|
132
|
+
if ax == bx && ay == by {
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
e2 := 2 * err
|
|
136
|
+
if e2 > -dy {
|
|
137
|
+
err -= dy
|
|
138
|
+
ax += sx
|
|
139
|
+
}
|
|
140
|
+
if e2 < dx {
|
|
141
|
+
err += dx
|
|
142
|
+
ay += sy
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// font5x7: 内置 5x7 点阵字库 (ASCII 数字/大写/常用标点; 小写映射为大写)。
|
|
148
|
+
type glyph [7]string
|
|
149
|
+
|
|
150
|
+
var font5x7 = map[rune]glyph{
|
|
151
|
+
' ': {" ", " ", " ", " ", " ", " ", " "},
|
|
152
|
+
'0': {" ### ", "# #", "# ##", "# # #", "## #", "# #", " ### "},
|
|
153
|
+
'1': {" # ", " ## ", " # ", " # ", " # ", " # ", " ### "},
|
|
154
|
+
'2': {" ### ", "# #", " #", " # ", " # ", " # ", "#####"},
|
|
155
|
+
'3': {"#####", " #", " # ", " ## ", " #", "# #", " ### "},
|
|
156
|
+
'4': {" # ", " ## ", " # # ", "# # ", "#####", " # ", " # "},
|
|
157
|
+
'5': {"#####", "# ", "#### ", " #", " #", "# #", " ### "},
|
|
158
|
+
'6': {" ## ", " # ", "# ", "#### ", "# #", "# #", " ### "},
|
|
159
|
+
'7': {"#####", " #", " # ", " # ", " # ", " # ", " # "},
|
|
160
|
+
'8': {" ### ", "# #", "# #", " ### ", "# #", "# #", " ### "},
|
|
161
|
+
'9': {" ### ", "# #", "# #", " ####", " #", " # ", " ## "},
|
|
162
|
+
'A': {" ### ", "# #", "# #", "#####", "# #", "# #", "# #"},
|
|
163
|
+
'B': {"#### ", "# #", "# #", "#### ", "# #", "# #", "#### "},
|
|
164
|
+
'C': {" ### ", "# #", "# ", "# ", "# ", "# #", " ### "},
|
|
165
|
+
'D': {"#### ", "# #", "# #", "# #", "# #", "# #", "#### "},
|
|
166
|
+
'E': {"#####", "# ", "# ", "#### ", "# ", "# ", "#####"},
|
|
167
|
+
'F': {"#####", "# ", "# ", "#### ", "# ", "# ", "# "},
|
|
168
|
+
'G': {" ### ", "# #", "# ", "# ###", "# #", "# #", " ####"},
|
|
169
|
+
'H': {"# #", "# #", "# #", "#####", "# #", "# #", "# #"},
|
|
170
|
+
'I': {" ### ", " # ", " # ", " # ", " # ", " # ", " ### "},
|
|
171
|
+
'J': {" #", " #", " #", " #", "# #", "# #", " ### "},
|
|
172
|
+
'K': {"# #", "# # ", "# # ", "## ", "# # ", "# # ", "# #"},
|
|
173
|
+
'L': {"# ", "# ", "# ", "# ", "# ", "# ", "#####"},
|
|
174
|
+
'M': {"# #", "## ##", "# # #", "# # #", "# #", "# #", "# #"},
|
|
175
|
+
'N': {"# #", "## #", "# # #", "# ##", "# #", "# #", "# #"},
|
|
176
|
+
'O': {" ### ", "# #", "# #", "# #", "# #", "# #", " ### "},
|
|
177
|
+
'P': {"#### ", "# #", "# #", "#### ", "# ", "# ", "# "},
|
|
178
|
+
'Q': {" ### ", "# #", "# #", "# #", "# # #", "# # ", " ## #"},
|
|
179
|
+
'R': {"#### ", "# #", "# #", "#### ", "# # ", "# # ", "# #"},
|
|
180
|
+
'S': {" ####", "# ", "# ", " ### ", " #", " #", "#### "},
|
|
181
|
+
'T': {"#####", " # ", " # ", " # ", " # ", " # ", " # "},
|
|
182
|
+
'U': {"# #", "# #", "# #", "# #", "# #", "# #", " ### "},
|
|
183
|
+
'V': {"# #", "# #", "# #", "# #", "# #", " # # ", " # "},
|
|
184
|
+
'W': {"# #", "# #", "# #", "# # #", "# # #", "## ##", "# #"},
|
|
185
|
+
'X': {"# #", "# #", " # # ", " # ", " # # ", "# #", "# #"},
|
|
186
|
+
'Y': {"# #", "# #", " # # ", " # ", " # ", " # ", " # "},
|
|
187
|
+
'Z': {"#####", " #", " # ", " # ", " # ", "# ", "#####"},
|
|
188
|
+
'.': {" ", " ", " ", " ", " ", " ## ", " ## "},
|
|
189
|
+
',': {" ", " ", " ", " ", " ## ", " # ", " # "},
|
|
190
|
+
':': {" ", " ## ", " ## ", " ", " ## ", " ## ", " "},
|
|
191
|
+
'-': {" ", " ", " ", "#####", " ", " ", " "},
|
|
192
|
+
'!': {" # ", " # ", " # ", " # ", " # ", " ", " # "},
|
|
193
|
+
'?': {" ### ", "# #", " #", " # ", " # ", " ", " # "},
|
|
194
|
+
'/': {" #", " #", " # ", " # ", " # ", "# ", "# "},
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
func (vm *vmState) canvasText(x, y uint64, s string) {
|
|
198
|
+
if curCanvas == nil {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
ox, oy := clampCoord(x), clampCoord(y)
|
|
202
|
+
s = strings.ToUpper(s)
|
|
203
|
+
b := curCanvas.Bounds()
|
|
204
|
+
px := ox
|
|
205
|
+
for _, r := range s {
|
|
206
|
+
g, ok := font5x7[r]
|
|
207
|
+
if !ok {
|
|
208
|
+
g = font5x7['?']
|
|
209
|
+
}
|
|
210
|
+
for row := 0; row < 7; row++ {
|
|
211
|
+
for col := 0; col < 5; col++ {
|
|
212
|
+
if g[row][col] == '#' {
|
|
213
|
+
cx := px + col
|
|
214
|
+
cy := oy + row
|
|
215
|
+
if cx >= b.Min.X && cx < b.Max.X && cy >= b.Min.Y && cy < b.Max.Y {
|
|
216
|
+
curCanvas.SetRGBA(cx, cy, curColor)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
px += 6
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
func (vm *vmState) canvasSave(path string) uint64 {
|
|
226
|
+
if curCanvas == nil {
|
|
227
|
+
return mask64 // -1
|
|
228
|
+
}
|
|
229
|
+
f, err := os.Create(path)
|
|
230
|
+
if err != nil {
|
|
231
|
+
return mask64
|
|
232
|
+
}
|
|
233
|
+
defer f.Close()
|
|
234
|
+
if err := png.Encode(f, curCanvas); err != nil {
|
|
235
|
+
return mask64
|
|
236
|
+
}
|
|
237
|
+
return 0
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// canvasShow 保存当前画布到临时 PNG 并用系统查看器打开 (跨平台 "窗口")。
|
|
241
|
+
func (vm *vmState) canvasShow() uint64 {
|
|
242
|
+
if curCanvas == nil {
|
|
243
|
+
return mask64 // -1
|
|
244
|
+
}
|
|
245
|
+
path := filepath.Join(os.TempDir(),
|
|
246
|
+
fmt.Sprintf("codecin_canvas_%d.png", time.Now().UnixNano()))
|
|
247
|
+
f, err := os.Create(path)
|
|
248
|
+
if err != nil {
|
|
249
|
+
return mask64
|
|
250
|
+
}
|
|
251
|
+
if err := png.Encode(f, curCanvas); err != nil {
|
|
252
|
+
_ = f.Close()
|
|
253
|
+
return mask64
|
|
254
|
+
}
|
|
255
|
+
_ = f.Close()
|
|
256
|
+
openViewer(path)
|
|
257
|
+
return 0
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func openViewer(path string) {
|
|
261
|
+
var cmd *exec.Cmd
|
|
262
|
+
switch runtime.GOOS {
|
|
263
|
+
case "windows":
|
|
264
|
+
cmd = exec.Command("cmd", "/c", "start", "", path)
|
|
265
|
+
case "darwin":
|
|
266
|
+
cmd = exec.Command("open", path)
|
|
267
|
+
default:
|
|
268
|
+
cmd = exec.Command("xdg-open", path)
|
|
269
|
+
}
|
|
270
|
+
_ = cmd.Start()
|
|
271
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
package engine
|
|
2
|
+
|
|
3
|
+
// CROM v3: magic 'CROM' + version u8 + mem_size u32 + flags u8 + crc32 u32
|
|
4
|
+
// + 2B reserved + payload (zlib 默认压缩, 与 Python zlib level 6 兼容)
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"bytes"
|
|
8
|
+
"compress/zlib"
|
|
9
|
+
"encoding/binary"
|
|
10
|
+
"hash/crc32"
|
|
11
|
+
"io"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
const cromVersion = 3
|
|
15
|
+
|
|
16
|
+
// cromMaxTrailer 是载荷允许超出头部 mem_size 的最大余量 (MMU 页表尾部元数据)。
|
|
17
|
+
// 必须与 Python 侧 codecin/crom.py: CROM_MAX_TRAILER 保持一致。
|
|
18
|
+
const cromMaxTrailer = 4 << 20
|
|
19
|
+
|
|
20
|
+
// CromPack 打包内存镜像为 CROM 字节流 (compress=true 时 zlib 压缩)。
|
|
21
|
+
func CromPack(data []byte, compress bool) []byte {
|
|
22
|
+
var payload []byte
|
|
23
|
+
flags := byte(0)
|
|
24
|
+
if compress {
|
|
25
|
+
flags = 0x01
|
|
26
|
+
var buf bytes.Buffer
|
|
27
|
+
// zlib.NewWriter 使用默认压缩级别 (flate.DefaultCompression = 6)
|
|
28
|
+
w := zlib.NewWriter(&buf)
|
|
29
|
+
_, _ = w.Write(data)
|
|
30
|
+
_ = w.Close()
|
|
31
|
+
payload = buf.Bytes()
|
|
32
|
+
} else {
|
|
33
|
+
payload = data
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
out := make([]byte, 16+len(payload))
|
|
37
|
+
copy(out[0:4], "CROM")
|
|
38
|
+
out[4] = cromVersion
|
|
39
|
+
binary.LittleEndian.PutUint32(out[5:9], uint32(len(data)))
|
|
40
|
+
out[9] = flags
|
|
41
|
+
binary.LittleEndian.PutUint32(out[10:14], crc32.ChecksumIEEE(payload))
|
|
42
|
+
// [14:16] reserved 0
|
|
43
|
+
copy(out[16:], payload)
|
|
44
|
+
return out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// CromUnpack 解包 CROM, 返回 (raw, flags, ok)。
|
|
48
|
+
func CromUnpack(data []byte) ([]byte, int, bool) {
|
|
49
|
+
if len(data) < 16 || string(data[0:4]) != "CROM" {
|
|
50
|
+
return nil, 0, false
|
|
51
|
+
}
|
|
52
|
+
if data[4] != cromVersion {
|
|
53
|
+
return nil, 0, false
|
|
54
|
+
}
|
|
55
|
+
memSize := binary.LittleEndian.Uint32(data[5:9])
|
|
56
|
+
flags := data[9]
|
|
57
|
+
checksum := binary.LittleEndian.Uint32(data[10:14])
|
|
58
|
+
payload := data[16:]
|
|
59
|
+
if crc32.ChecksumIEEE(payload) != checksum {
|
|
60
|
+
return nil, 0, false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var raw []byte
|
|
64
|
+
if flags&0x01 != 0 {
|
|
65
|
+
r, err := zlib.NewReader(bytes.NewReader(payload))
|
|
66
|
+
if err != nil {
|
|
67
|
+
return nil, 0, false
|
|
68
|
+
}
|
|
69
|
+
// 解压上限取自文件头声明的 mem_size + MMU 尾部余量: 防止 zip bomb
|
|
70
|
+
// (旧实现 io.ReadAll 无上限, 65KB 的合法 CROM 可解出 64MB+)。
|
|
71
|
+
limit := int64(memSize) + cromMaxTrailer
|
|
72
|
+
limited := io.LimitReader(r, limit+1)
|
|
73
|
+
raw, err = io.ReadAll(limited)
|
|
74
|
+
_ = r.Close()
|
|
75
|
+
if err != nil {
|
|
76
|
+
return nil, 0, false
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
raw = payload
|
|
80
|
+
}
|
|
81
|
+
// mem_size 是物理内存长度; 之后允许跟一段 MMU 页表尾部元数据
|
|
82
|
+
if uint32(len(raw)) < memSize || len(raw) > int(memSize)+cromMaxTrailer {
|
|
83
|
+
return nil, 0, false
|
|
84
|
+
}
|
|
85
|
+
return raw, int(flags), true
|
|
86
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package engine
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/binary"
|
|
5
|
+
"fmt"
|
|
6
|
+
"math"
|
|
7
|
+
|
|
8
|
+
"codecin-native/ir"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// UCBC 字节码编码 (与 Python codecin/native.py encode_program 严格一致)。
|
|
12
|
+
const (
|
|
13
|
+
bcMagic = "UCBC"
|
|
14
|
+
bcVersion = 1
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// condCode: 条件助记符 -> 编号 (与 Python Cond.NAMES 一致)
|
|
18
|
+
var condCode = map[string]uint8{
|
|
19
|
+
"EQ": 0, "NE": 1, "CS": 2, "CC": 3, "MI": 4, "PL": 5, "VS": 6, "VC": 7,
|
|
20
|
+
"HI": 8, "LS": 9, "GE": 10, "LT": 11, "GT": 12, "LE": 13, "AL": 14, "NV": 15,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// EncodeProgram 把 IR 程序编码为 UCBC 字节码。
|
|
24
|
+
func EncodeProgram(prog ir.Program, entry int) ([]byte, error) {
|
|
25
|
+
labels := map[string]int{}
|
|
26
|
+
for k, v := range prog.Labels {
|
|
27
|
+
labels[k] = v
|
|
28
|
+
}
|
|
29
|
+
for k, v := range prog.DataLabels {
|
|
30
|
+
labels[k] = v
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
buf := make([]byte, 0, 13+len(prog.Instructions)*8)
|
|
34
|
+
buf = append(buf, bcMagic...)
|
|
35
|
+
buf = append(buf, bcVersion)
|
|
36
|
+
var tmp [8]byte
|
|
37
|
+
binary.LittleEndian.PutUint32(tmp[:4], uint32(entry&0xFFFFFFFF))
|
|
38
|
+
buf = append(buf, tmp[:4]...)
|
|
39
|
+
binary.LittleEndian.PutUint32(tmp[:4], uint32(len(prog.Instructions)&0xFFFFFFFF))
|
|
40
|
+
buf = append(buf, tmp[:4]...)
|
|
41
|
+
|
|
42
|
+
for _, ins := range prog.Instructions {
|
|
43
|
+
opv, ok := opcodeByName[ins.Op]
|
|
44
|
+
if !ok {
|
|
45
|
+
return nil, fmt.Errorf("unknown opcode: %s", ins.Op)
|
|
46
|
+
}
|
|
47
|
+
buf = append(buf, opv, byte(len(ins.Args)))
|
|
48
|
+
for _, a := range ins.Args {
|
|
49
|
+
kind, value, extra, err := encodeOperand(a, labels)
|
|
50
|
+
if err != nil {
|
|
51
|
+
return nil, err
|
|
52
|
+
}
|
|
53
|
+
buf = append(buf, kind)
|
|
54
|
+
binary.LittleEndian.PutUint64(tmp[:8], uint64(value))
|
|
55
|
+
buf = append(buf, tmp[:8]...)
|
|
56
|
+
binary.LittleEndian.PutUint64(tmp[:8], uint64(extra))
|
|
57
|
+
buf = append(buf, tmp[:8]...)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return buf, nil
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func encodeOperand(op ir.Operand, labels map[string]int) (kind uint8, value, extra int64, err error) {
|
|
64
|
+
switch op.Kind {
|
|
65
|
+
case "reg":
|
|
66
|
+
return kindReg, op.V, 0, nil
|
|
67
|
+
case "imm":
|
|
68
|
+
return kindImm, op.V, 0, nil
|
|
69
|
+
case "vec":
|
|
70
|
+
return kindVec, op.V, 0, nil
|
|
71
|
+
case "veclane":
|
|
72
|
+
return kindVecLane, op.V, op.Off, nil
|
|
73
|
+
case "mem":
|
|
74
|
+
return kindMem, op.V, op.Off, nil
|
|
75
|
+
case "cond":
|
|
76
|
+
c, ok := condCode[op.Name]
|
|
77
|
+
if !ok {
|
|
78
|
+
return 0, 0, 0, fmt.Errorf("unknown cond: %s", op.Name)
|
|
79
|
+
}
|
|
80
|
+
return kindCond, int64(c), 0, nil
|
|
81
|
+
case "float":
|
|
82
|
+
return kindFloat, int64(math.Float64bits(op.F)), 0, nil
|
|
83
|
+
case "str":
|
|
84
|
+
return kindStr, op.V, 0, nil
|
|
85
|
+
case "label":
|
|
86
|
+
addr, ok := labels[op.Name]
|
|
87
|
+
if !ok {
|
|
88
|
+
return 0, 0, 0, fmt.Errorf("undefined label: %s", op.Name)
|
|
89
|
+
}
|
|
90
|
+
return kindImm, int64(addr), 0, nil
|
|
91
|
+
}
|
|
92
|
+
return 0, 0, 0, fmt.Errorf("unknown operand kind: %s", op.Kind)
|
|
93
|
+
}
|