all repos

scratch @ 2b64f4e6f2d35ae68bf39cec616a782153335bc0

⭐ me doing recreational ~~drugs~~ programming

scratch/blo/blox.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
blo: add readme; make gopls analyzer happy, 1 month ago
1
package main
2
3
import (
4
	"errors"
5
	"fmt"
6
	"io"
7
	"net/http"
8
	"os"
9
	"strings"
10
)
11
12
var escope = EvalScope{
13
	Vars: map[string]Expr{
14
		"my_name": {
15
			Type:  ExprStr,
16
			AsStr: "Olex",
17
		},
18
	},
19
	Funcs: map[string]Func{
20
		"let": func(ctx *EvalContext, args []Expr) (Expr, error) {
21
			if len(args) != 2 {
22
				return Expr{}, errors.New("let() expects two arguments")
23
			}
24
25
			if args[0].Type != ExprVar {
26
				return Expr{}, errors.New("first argument of let() has to be variable name")
27
			}
28
29
			name := args[0].AsVar
30
			value, err := ctx.EvalExpr(args[1])
31
			if err != nil {
32
				return Expr{}, err
33
			}
34
35
			ctx.TopScope().Vars[name] = value
36
			return Expr{}, nil
37
		},
38
		"define": func(ctx *EvalContext, args []Expr) (Expr, error) {
39
			if len(args) < 2 {
40
				return Expr{}, errors.New("define() expects at least 2 arguments")
41
			}
42
43
			if args[0].Type != ExprVar {
44
				return Expr{}, errors.New("define(): first argument must be the name of the function")
45
			}
46
47
			funName := args[0].AsVar
48
			if args[1].Type != ExprFuncall || args[1].AsFuncall.Name != "args" {
49
				return Expr{}, errors.New("define(): second argument must be the argument list")
50
			}
51
52
			funArgs := args[1].AsFuncall.Args
53
			for _, funArg := range funArgs {
54
				if funArg.Type != ExprVar {
55
					return Expr{}, errors.New("define(): argument list must consist of only variable names")
56
				}
57
			}
58
59
			ctx.TopScope().Funcs[funName] = func(context *EvalContext, callArgs []Expr) (Expr, error) {
60
				scope := EvalScope{
61
					Vars:  map[string]Expr{},
62
					Funcs: map[string]Func{},
63
				}
64
65
				if len(callArgs) != len(funArgs) {
66
					return Expr{}, fmt.Errorf("%s(): expected %d arguments but provided %d", funName, len(funArgs), len(args))
67
				}
68
69
				for index := range callArgs {
70
					scope.Vars[funArgs[index].AsVar] = callArgs[index]
71
				}
72
73
				context.PushScope(scope)
74
				for _, stmt := range args[2:] {
75
					_, err := context.EvalExpr(stmt)
76
					if err != nil {
77
						return Expr{}, err
78
					}
79
				}
80
				context.PopScope()
81
82
				return Expr{}, nil
83
			}
84
85
			return Expr{}, nil
86
		},
87
		"say": func(context *EvalContext, args []Expr) (Expr, error) {
88
			for _, arg := range args {
89
				val, err := context.EvalExpr(arg)
90
				if err != nil {
91
					return Expr{}, err
92
				}
93
94
				switch val.Type {
95
				case ExprStr:
96
					fmt.Printf("%s", val.AsStr)
97
98
				case ExprInt:
99
					fmt.Printf("%d", val.AsInt)
100
				default:
101
					return Expr{}, errors.New("say() expects its arguments to be strings or numbers")
102
				}
103
			}
104
			fmt.Printf("\n")
105
			return Expr{}, nil
106
		},
107
		"http": func(context *EvalContext, args []Expr) (Expr, error) {
108
			var url strings.Builder
109
			for _, arg := range args {
110
				val, err := context.EvalExpr(arg)
111
				if err != nil {
112
					return Expr{}, err
113
				}
114
				if val.Type != ExprStr {
115
					return Expr{}, errors.New("http() expects its arguments to be strings")
116
				}
117
				fmt.Fprint(&url, val.AsStr)
118
			}
119
120
			resp, err := http.Get(url.String())
121
			if err != nil {
122
				return Expr{}, err
123
			}
124
			defer resp.Body.Close()
125
126
			body, err := io.ReadAll(resp.Body)
127
			if err != nil {
128
				return Expr{}, err
129
			}
130
131
			return Expr{
132
				Type:  ExprStr,
133
				AsStr: string(body),
134
			}, nil
135
		},
136
	},
137
}
138
139
func main() {
140
	ctx := EvalContext{}
141
	ctx.PushScope(escope)
142
143
	if len(os.Args) < 2 {
144
		fmt.Fprintf(os.Stderr, "ERROR: no input is provided\n")
145
		os.Exit(1)
146
	}
147
148
	fpath := os.Args[1]
149
	content, err := os.ReadFile(fpath)
150
	if err != nil {
151
		fmt.Fprintf(os.Stderr, "ERROR: could not read file %s: %s\n", fpath, err)
152
		os.Exit(1)
153
	}
154
155
	lexer := NewLexer([]rune(string(content)), fpath)
156
	exprs, err := ParseExprs(&lexer)
157
	if err != nil {
158
		fmt.Fprintln(os.Stderr, err)
159
		os.Exit(1)
160
	}
161
162
	for _, expr := range exprs {
163
		if _, err := ctx.EvalExpr(expr); err != nil {
164
			fmt.Fprintln(os.Stderr, err)
165
			os.Exit(1)
166
		}
167
	}
168
}