all repos

scratch @ 6fd17573ac7a1d0ffb3ce5749e3e6db32ab8f3ff

⭐ me doing recreational ~~drugs~~ programming
4 files changed, 676 insertions(+), 0 deletions(-)
random language
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-04-13 17:05:34 +0300
Change ID: swmxkvulmqlmuqswptzkrtpmxtsntwnk
Parent: 753ee96
A blo/blo.go
···
                
                1
                +package main

              
                
                2
                +

              
                
                3
                +import (

              
                
                4
                +	"errors"

              
                
                5
                +	"fmt"

              
                
                6
                +	"strconv"

              
                
                7
                +	"strings"

              
                
                8
                +	"unicode"

              
                
                9
                +)

              
                
                10
                +

              
                
                11
                +type TokenType int

              
                
                12
                +

              
                
                13
                +const (

              
                
                14
                +	TokenInvalid TokenType = iota

              
                
                15
                +	TokenSym

              
                
                16
                +	TokenNum

              
                
                17
                +	TokenStr

              
                
                18
                +	TokenOParen

              
                
                19
                +	TokenCParen

              
                
                20
                +	TokenComma

              
                
                21
                +)

              
                
                22
                +

              
                
                23
                +var TokenTypeName = map[TokenType]string{

              
                
                24
                +	TokenInvalid: "TokenInvalid",

              
                
                25
                +	TokenSym:     "TokenSym",

              
                
                26
                +	TokenNum:     "TokenNum",

              
                
                27
                +	TokenStr:     "TokenStr",

              
                
                28
                +	TokenOParen:  "TokenOParen",

              
                
                29
                +	TokenCParen:  "TokenCParen",

              
                
                30
                +	TokenComma:   "TokenComma",

              
                
                31
                +}

              
                
                32
                +

              
                
                33
                +type Token struct {

              
                
                34
                +	Type TokenType

              
                
                35
                +	Text []rune

              
                
                36
                +	Loc  Loc

              
                
                37
                +}

              
                
                38
                +

              
                
                39
                +type Loc struct {

              
                
                40
                +	Filepath string

              
                
                41
                +	Row, Col int

              
                
                42
                +}

              
                
                43
                +

              
                
                44
                +func (l Loc) String() string { return fmt.Sprintf("%s:%d:%d", l.Filepath, l.Row, l.Col) }

              
                
                45
                +

              
                
                46
                +type DiagError struct {

              
                
                47
                +	Loc Loc

              
                
                48
                +	Err error

              
                
                49
                +}

              
                
                50
                +

              
                
                51
                +func (e *DiagError) Unwrap() error { return e.Err }

              
                
                52
                +func (e *DiagError) Error() string {

              
                
                53
                +	return fmt.Sprintf("%s: ERROR: %s", e.Loc, e.Err)

              
                
                54
                +}

              
                
                55
                +

              
                
                56
                +var (

              
                
                57
                +	ErrLexerEOF           = errors.New("Lexer: End of file")

              
                
                58
                +	ErrLexerUnclosedStr   = errors.New("Lexer: Unclosed String")

              
                
                59
                +	ErrLexerInvalidEscape = errors.New("Lexer: Invalid Escape Sequence")

              
                
                60
                +	ErrLexerInvalidToken  = errors.New("Lexer: Invalid Token")

              
                
                61
                +)

              
                
                62
                +

              
                
                63
                +type Lexer struct {

              
                
                64
                +	Filepath string

              
                
                65
                +	Content  []rune // TODO: bytes

              
                
                66
                +	Row      int

              
                
                67
                +	Cur      int

              
                
                68
                +	Bol      int

              
                
                69
                +	PeekTok  Token

              
                
                70
                +	PeekErr  error

              
                
                71
                +	PeekFull bool

              
                
                72
                +}

              
                
                73
                +

              
                
                74
                +func NewLexer(content []rune, filePath string) Lexer {

              
                
                75
                +	return Lexer{

              
                
                76
                +		Filepath: filePath,

              
                
                77
                +		Content:  content,

              
                
                78
                +	}

              
                
                79
                +}

              
                
                80
                +

              
                
                81
                +func (l *Lexer) ChopChar() {

              
                
                82
                +	if l.Cur < len(l.Content) {

              
                
                83
                +		x := l.Content[l.Cur]

              
                
                84
                +		l.Cur += 1

              
                
                85
                +		if x == '\n' {

              
                
                86
                +			l.Bol = l.Cur

              
                
                87
                +			l.Row += 1

              
                
                88
                +		}

              
                
                89
                +	}

              
                
                90
                +}

              
                
                91
                +

              
                
                92
                +func (l *Lexer) TrimLeft() {

              
                
                93
                +	for l.Cur < len(l.Content) && unicode.IsSpace(l.Content[l.Cur]) {

              
                
                94
                +		l.ChopChar()

              
                
                95
                +	}

              
                
                96
                +}

              
                
                97
                +

              
                
                98
                +func (l Lexer) StartsWith(prefix []rune) bool {

              
                
                99
                +	if l.Cur+len(prefix) > len(l.Content) {

              
                
                100
                +		return false

              
                
                101
                +	}

              
                
                102
                +

              
                
                103
                +	for i := range prefix {

              
                
                104
                +		if prefix[i] != l.Content[l.Cur+i] {

              
                
                105
                +			return false

              
                
                106
                +		}

              
                
                107
                +	}

              
                
                108
                +

              
                
                109
                +	return true

              
                
                110
                +}

              
                
                111
                +

              
                
                112
                +func (l *Lexer) Loc() Loc {

              
                
                113
                +	return Loc{

              
                
                114
                +		Filepath: l.Filepath,

              
                
                115
                +		Row:      l.Row + 1,

              
                
                116
                +		Col:      l.Cur - l.Bol + 1,

              
                
                117
                +	}

              
                
                118
                +}

              
                
                119
                +

              
                
                120
                +func (l *Lexer) ChopWhile(p func(rune) bool) (result []rune) {

              
                
                121
                +	for l.Cur < len(l.Content) && p(l.Content[l.Cur]) {

              
                
                122
                +		result = append(result, l.Content[l.Cur])

              
                
                123
                +		l.ChopChar()

              
                
                124
                +	}

              
                
                125
                +	return

              
                
                126
                +}

              
                
                127
                +

              
                
                128
                +func (l *Lexer) ChopToken() (Token, error) {

              
                
                129
                +	t := Token{}

              
                
                130
                +

              
                
                131
                +	l.TrimLeft()

              
                
                132
                +	for l.Cur < len(l.Content) && l.StartsWith([]rune("--")) {

              
                
                133
                +		for l.Cur < len(l.Content) && l.Content[l.Cur] != '\n' {

              
                
                134
                +			l.ChopChar()

              
                
                135
                +		}

              
                
                136
                +		l.TrimLeft()

              
                
                137
                +	}

              
                
                138
                +

              
                
                139
                +	t.Loc = l.Loc()

              
                
                140
                +

              
                
                141
                +	if l.Cur >= len(l.Content) {

              
                
                142
                +		return t, ErrLexerEOF

              
                
                143
                +	}

              
                
                144
                +

              
                
                145
                +	first := l.Content[l.Cur]

              
                
                146
                +

              
                
                147
                +	ps := []rune("(),")

              
                
                148
                +	ts := []TokenType{TokenOParen, TokenCParen, TokenComma}

              
                
                149
                +	for i := range ps {

              
                
                150
                +		if first == ps[i] {

              
                
                151
                +			t.Type = ts[i]

              
                
                152
                +			t.Text = []rune{ps[i]}

              
                
                153
                +			l.ChopChar()

              
                
                154
                +			return t, nil

              
                
                155
                +		}

              
                
                156
                +	}

              
                
                157
                +

              
                
                158
                +	if unicode.IsDigit(first) {

              
                
                159
                +		t.Type = TokenNum

              
                
                160
                +		t.Text = l.ChopWhile(unicode.IsDigit)

              
                
                161
                +		return t, nil

              
                
                162
                +	}

              
                
                163
                +

              
                
                164
                +	if unicode.IsLetter(first) || first == '_' {

              
                
                165
                +		t.Type = TokenSym

              
                
                166
                +		t.Text = l.ChopWhile(func(x rune) bool {

              
                
                167
                +			return unicode.IsLetter(x) || unicode.IsDigit(x) || x == '_'

              
                
                168
                +		})

              
                
                169
                +		return t, nil

              
                
                170
                +	}

              
                
                171
                +

              
                
                172
                +	if first == '"' {

              
                
                173
                +		l.ChopChar()

              
                
                174
                +

              
                
                175
                +		t.Type = TokenStr

              
                
                176
                +

              
                
                177
                +		for l.Cur < len(l.Content) && l.Content[l.Cur] != '"' {

              
                
                178
                +			if l.Content[l.Cur] == '\\' {

              
                
                179
                +				l.ChopChar()

              
                
                180
                +				if l.Cur >= len(l.Content) {

              
                
                181
                +					return t, &DiagError{

              
                
                182
                +						Loc: l.Loc(),

              
                
                183
                +						Err: ErrLexerUnclosedStr,

              
                
                184
                +					}

              
                
                185
                +				}

              
                
                186
                +				if l.Content[l.Cur] == '"' {

              
                
                187
                +					t.Text = append(t.Text, '"')

              
                
                188
                +					l.ChopChar()

              
                
                189
                +				} else {

              
                
                190
                +					loc := l.Loc()

              
                
                191
                +					l.ChopChar()

              
                
                192
                +					return t, &DiagError{

              
                
                193
                +						Loc: loc,

              
                
                194
                +						Err: fmt.Errorf("%w: %c", ErrLexerInvalidEscape, l.Content[l.Cur]),

              
                
                195
                +					}

              
                
                196
                +				}

              
                
                197
                +			} else {

              
                
                198
                +				t.Text = append(t.Text, l.Content[l.Cur])

              
                
                199
                +				l.ChopChar()

              
                
                200
                +			}

              
                
                201
                +		}

              
                
                202
                +

              
                
                203
                +		if l.Cur >= len(l.Content) {

              
                
                204
                +			return t, &DiagError{

              
                
                205
                +				Loc: l.Loc(),

              
                
                206
                +				Err: ErrLexerUnclosedStr,

              
                
                207
                +			}

              
                
                208
                +		}

              
                
                209
                +

              
                
                210
                +		l.ChopChar()

              
                
                211
                +		return t, nil

              
                
                212
                +	}

              
                
                213
                +

              
                
                214
                +	l.ChopChar()

              
                
                215
                +	return t, &DiagError{

              
                
                216
                +		Loc: l.Loc(),

              
                
                217
                +		Err: fmt.Errorf("%w: %c", ErrLexerInvalidToken, first),

              
                
                218
                +	}

              
                
                219
                +}

              
                
                220
                +

              
                
                221
                +func (l *Lexer) Peek() (Token, error) {

              
                
                222
                +	if !l.PeekFull {

              
                
                223
                +		l.PeekTok, l.PeekErr = l.ChopToken()

              
                
                224
                +		l.PeekFull = true

              
                
                225
                +	}

              
                
                226
                +	return l.PeekTok, l.PeekErr

              
                
                227
                +}

              
                
                228
                +

              
                
                229
                +func (l *Lexer) Next() (Token, error) {

              
                
                230
                +	if l.PeekFull {

              
                
                231
                +		l.PeekFull = false

              
                
                232
                +	} else {

              
                
                233
                +		l.PeekTok, l.PeekErr = l.ChopToken()

              
                
                234
                +	}

              
                
                235
                +	return l.PeekTok, l.PeekErr

              
                
                236
                +}

              
                
                237
                +

              
                
                238
                +type ExprType int

              
                
                239
                +

              
                
                240
                +const (

              
                
                241
                +	ExprVoid ExprType = iota

              
                
                242
                +	ExprInt

              
                
                243
                +	ExprStr

              
                
                244
                +	ExprVar

              
                
                245
                +	ExprFuncall

              
                
                246
                +)

              
                
                247
                +

              
                
                248
                +type Expr struct {

              
                
                249
                +	Type ExprType

              
                
                250
                +	Loc  Loc

              
                
                251
                +

              
                
                252
                +	AsInt     int

              
                
                253
                +	AsStr     string

              
                
                254
                +	AsVar     string

              
                
                255
                +	AsFuncall Funcall

              
                
                256
                +}

              
                
                257
                +

              
                
                258
                +type Funcall struct {

              
                
                259
                +	Name string

              
                
                260
                +	Args []Expr

              
                
                261
                +}

              
                
                262
                +

              
                
                263
                +func (funcall *Funcall) String() string {

              
                
                264
                +	var result strings.Builder

              
                
                265
                +	fmt.Fprintf(&result, "%s(", funcall.Name)

              
                
                266
                +	for i, arg := range funcall.Args {

              
                
                267
                +		if i > 0 {

              
                
                268
                +			fmt.Fprintf(&result, ", ")

              
                
                269
                +		}

              
                
                270
                +		fmt.Fprintf(&result, "%s", arg.String())

              
                
                271
                +	}

              
                
                272
                +	fmt.Fprintf(&result, ")")

              
                
                273
                +	return result.String()

              
                
                274
                +}

              
                
                275
                +

              
                
                276
                +func (expr *Expr) Dump(level int) {

              
                
                277
                +	for i := 0; i < level; i += 1 {

              
                
                278
                +		fmt.Printf("  ")

              
                
                279
                +	}

              
                
                280
                +

              
                
                281
                +	switch expr.Type {

              
                
                282
                +	case ExprVoid:

              
                
                283
                +		fmt.Printf("Void\n")

              
                
                284
                +	case ExprInt:

              
                
                285
                +		fmt.Printf("Int: %d\n", expr.AsInt)

              
                
                286
                +	case ExprStr:

              
                
                287
                +		fmt.Printf("Str: \"%s\"\n", expr.AsStr) // TODO: escape strings

              
                
                288
                +	case ExprVar:

              
                
                289
                +		fmt.Printf("Var: %s\n", expr.AsVar)

              
                
                290
                +	case ExprFuncall:

              
                
                291
                +		fmt.Printf("Funcall: %s\n", expr.AsFuncall.Name)

              
                
                292
                +		for _, arg := range expr.AsFuncall.Args {

              
                
                293
                +			arg.Dump(level + 1)

              
                
                294
                +		}

              
                
                295
                +	}

              
                
                296
                +}

              
                
                297
                +

              
                
                298
                +func (expr *Expr) String() string {

              
                
                299
                +	switch expr.Type {

              
                
                300
                +	case ExprVoid:

              
                
                301
                +		return ""

              
                
                302
                +	case ExprInt:

              
                
                303
                +		return fmt.Sprintf("%d", expr.AsInt)

              
                
                304
                +	case ExprStr:

              
                
                305
                +		return fmt.Sprintf("\"%s\"", expr.AsStr) // TODO: escape string

              
                
                306
                +	case ExprVar:

              
                
                307
                +		return expr.AsVar

              
                
                308
                +	case ExprFuncall:

              
                
                309
                +		return expr.AsFuncall.String()

              
                
                310
                +	default:

              
                
                311
                +		panic("unreachable")

              
                
                312
                +	}

              
                
                313
                +}

              
                
                314
                +

              
                
                315
                +var (

              
                
                316
                +	ErrParserUnexpectedToken = errors.New("Parser: Unexpected Token")

              
                
                317
                +	ErrParserUnclosedFuncall = errors.New("Parser: Unclosed Funcall")

              
                
                318
                +)

              
                
                319
                +

              
                
                320
                +func ParseExpr(l *Lexer) (Expr, error) {

              
                
                321
                +	t, err := l.Next()

              
                
                322
                +	if err != nil {

              
                
                323
                +		return Expr{}, err

              
                
                324
                +	}

              
                
                325
                +

              
                
                326
                +	switch t.Type {

              
                
                327
                +	case TokenSym:

              
                
                328
                +		oparen, err := l.Peek()

              
                
                329
                +		if err != nil || oparen.Type != TokenOParen {

              
                
                330
                +			return Expr{

              
                
                331
                +				Type:  ExprVar,

              
                
                332
                +				Loc:   t.Loc,

              
                
                333
                +				AsVar: string(t.Text),

              
                
                334
                +			}, nil

              
                
                335
                +		} else {

              
                
                336
                +			l.Next()

              
                
                337
                +			cparen, err := l.Peek()

              
                
                338
                +

              
                
                339
                +			if err == nil && cparen.Type == TokenCParen {

              
                
                340
                +				l.Next()

              
                
                341
                +				return Expr{

              
                
                342
                +					Type:      ExprFuncall,

              
                
                343
                +					Loc:       t.Loc,

              
                
                344
                +					AsFuncall: Funcall{Name: string(t.Text)},

              
                
                345
                +				}, nil

              
                
                346
                +			}

              
                
                347
                +

              
                
                348
                +			arg, err := ParseExpr(l)

              
                
                349
                +			if err != nil {

              
                
                350
                +				return Expr{}, err

              
                
                351
                +			}

              
                
                352
                +

              
                
                353
                +			args := []Expr{arg}

              
                
                354
                +

              
                
                355
                +			comma, err := l.Next()

              
                
                356
                +			for err == nil && comma.Type == TokenComma {

              
                
                357
                +				arg, err = ParseExpr(l)

              
                
                358
                +				if err != nil {

              
                
                359
                +					return Expr{}, err

              
                
                360
                +				}

              
                
                361
                +				args = append(args, arg)

              
                
                362
                +				comma, err = l.Next()

              
                
                363
                +			}

              
                
                364
                +

              
                
                365
                +			if err == ErrLexerEOF || comma.Type != TokenCParen {

              
                
                366
                +				return Expr{}, &DiagError{Loc: comma.Loc, Err: ErrParserUnclosedFuncall}

              
                
                367
                +			}

              
                
                368
                +

              
                
                369
                +			return Expr{

              
                
                370
                +				Type: ExprFuncall,

              
                
                371
                +				Loc:  t.Loc,

              
                
                372
                +				AsFuncall: Funcall{

              
                
                373
                +					Name: string(t.Text),

              
                
                374
                +					Args: args,

              
                
                375
                +				},

              
                
                376
                +			}, nil

              
                
                377
                +		}

              
                
                378
                +

              
                
                379
                +	case TokenNum:

              
                
                380
                +		s := string(t.Text)

              
                
                381
                +		x, err := strconv.Atoi(s)

              
                
                382
                +		if err != nil {

              
                
                383
                +			return Expr{}, &DiagError{Loc: t.Loc, Err: err}

              
                
                384
                +		}

              
                
                385
                +		return Expr{

              
                
                386
                +			Type:  ExprInt,

              
                
                387
                +			Loc:   t.Loc,

              
                
                388
                +			AsInt: x,

              
                
                389
                +		}, nil

              
                
                390
                +

              
                
                391
                +	case TokenStr:

              
                
                392
                +		return Expr{

              
                
                393
                +			Type:  ExprStr,

              
                
                394
                +			Loc:   t.Loc,

              
                
                395
                +			AsStr: string(t.Text),

              
                
                396
                +		}, nil

              
                
                397
                +	}

              
                
                398
                +

              
                
                399
                +	return Expr{}, &DiagError{

              
                
                400
                +		Loc: t.Loc,

              
                
                401
                +		Err: fmt.Errorf("%w: %s", ErrParserUnexpectedToken, TokenTypeName[t.Type]),

              
                
                402
                +	}

              
                
                403
                +}

              
                
                404
                +

              
                
                405
                +func ParseExprs(l *Lexer) ([]Expr, error) {

              
                
                406
                +	exprs := []Expr{}

              
                
                407
                +	for {

              
                
                408
                +		expr, err := ParseExpr(l)

              
                
                409
                +		if err != nil {

              
                
                410
                +			if errors.Is(err, ErrLexerEOF) {

              
                
                411
                +				err = nil

              
                
                412
                +			}

              
                
                413
                +			return exprs, err

              
                
                414
                +		}

              
                
                415
                +		exprs = append(exprs, expr)

              
                
                416
                +	}

              
                
                417
                +}

              
                
                418
                +

              
                
                419
                +type (

              
                
                420
                +	Func = func(context *EvalContext, args []Expr) (Expr, error)

              
                
                421
                +

              
                
                422
                +	EvalContext struct{ Scopes []EvalScope }

              
                
                423
                +	EvalScope   struct {

              
                
                424
                +		Vars  map[string]Expr

              
                
                425
                +		Funcs map[string]Func

              
                
                426
                +	}

              
                
                427
                +)

              
                
                428
                +

              
                
                429
                +func (e EvalContext) LookupVar(name string) (Expr, bool) {

              
                
                430
                +	scopes := e.Scopes

              
                
                431
                +	for len(scopes) > 0 {

              
                
                432
                +		n := len(scopes)

              
                
                433
                +		varr, ok := scopes[n-1].Vars[name]

              
                
                434
                +		if ok {

              
                
                435
                +			return varr, true

              
                
                436
                +		}

              
                
                437
                +		scopes = scopes[:n-1]

              
                
                438
                +	}

              
                
                439
                +	return Expr{}, false

              
                
                440
                +}

              
                
                441
                +

              
                
                442
                +func (e EvalContext) LookupFunc(name string) (Func, bool) {

              
                
                443
                +	scopes := e.Scopes

              
                
                444
                +	for len(scopes) > 0 {

              
                
                445
                +		n := len(scopes)

              
                
                446
                +		fun, ok := scopes[n-1].Funcs[name]

              
                
                447
                +		if ok {

              
                
                448
                +			return fun, true

              
                
                449
                +		}

              
                
                450
                +		scopes = scopes[:n-1]

              
                
                451
                +	}

              
                
                452
                +	return nil, false

              
                
                453
                +}

              
                
                454
                +

              
                
                455
                +func (e *EvalContext) PushScope(scope EvalScope) { e.Scopes = append(e.Scopes, scope) }

              
                
                456
                +func (e *EvalContext) PopScope()                 { e.Scopes = e.Scopes[0 : len(e.Scopes)-1] }

              
                
                457
                +

              
                
                458
                +func (e *EvalContext) TopScope() *EvalScope {

              
                
                459
                +	length := len(e.Scopes)

              
                
                460
                +	if length <= 0 {

              
                
                461
                +		panic("No scopes found")

              
                
                462
                +	}

              
                
                463
                +	return &e.Scopes[length-1]

              
                
                464
                +}

              
                
                465
                +

              
                
                466
                +var (

              
                
                467
                +	ErrRuntimeUnknownVar = errors.New("unknown runtime variable")

              
                
                468
                +	ErrRuntimeUnknownFun = errors.New("unknown runtime function")

              
                
                469
                +)

              
                
                470
                +

              
                
                471
                +func (e *EvalContext) EvalExpr(expr Expr) (Expr, error) {

              
                
                472
                +	switch expr.Type {

              
                
                473
                +	case ExprVoid, ExprInt, ExprStr:

              
                
                474
                +		return expr, nil

              
                
                475
                +

              
                
                476
                +	case ExprVar:

              
                
                477
                +		v, ok := e.LookupVar(expr.AsVar)

              
                
                478
                +		if !ok {

              
                
                479
                +			return Expr{}, fmt.Errorf("%s: ERROR: %w: %s", expr.Loc, ErrRuntimeUnknownVar, expr.AsVar)

              
                
                480
                +		}

              
                
                481
                +		return e.EvalExpr(v)

              
                
                482
                +

              
                
                483
                +	case ExprFuncall:

              
                
                484
                +		fn, ok := e.LookupFunc(expr.AsFuncall.Name)

              
                
                485
                +		if !ok {

              
                
                486
                +			return Expr{}, fmt.Errorf("%s: ERROR: %w: %s", expr.Loc, ErrRuntimeUnknownFun, expr.AsFuncall.Name)

              
                
                487
                +		}

              
                
                488
                +		return fn(e, expr.AsFuncall.Args)

              
                
                489
                +

              
                
                490
                +	default:

              
                
                491
                +		panic("unreachable")

              
                
                492
                +	}

              
                
                493
                +}

              
A blo/blox.go
···
                
                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
                +

              
                
                39
                +		"define": func(ctx *EvalContext, args []Expr) (Expr, error) {

              
                
                40
                +			if len(args) < 2 {

              
                
                41
                +				return Expr{}, errors.New("define() expects at least 2 arguments")

              
                
                42
                +			}

              
                
                43
                +

              
                
                44
                +			if args[0].Type != ExprVar {

              
                
                45
                +				return Expr{}, errors.New("define(): first argument must be the name of the function")

              
                
                46
                +			}

              
                
                47
                +

              
                
                48
                +			funName := args[0].AsVar

              
                
                49
                +			if args[1].Type != ExprFuncall || args[1].AsFuncall.Name != "args" {

              
                
                50
                +				return Expr{}, errors.New("define(): second argument must be the argument list")

              
                
                51
                +			}

              
                
                52
                +

              
                
                53
                +			funArgs := args[1].AsFuncall.Args

              
                
                54
                +			for _, funArg := range funArgs {

              
                
                55
                +				if funArg.Type != ExprVar {

              
                
                56
                +					return Expr{}, errors.New("define(): argument list must consist of only variable names")

              
                
                57
                +				}

              
                
                58
                +			}

              
                
                59
                +

              
                
                60
                +			ctx.TopScope().Funcs[funName] = func(context *EvalContext, callArgs []Expr) (Expr, error) {

              
                
                61
                +				scope := EvalScope{

              
                
                62
                +					Vars:  map[string]Expr{},

              
                
                63
                +					Funcs: map[string]Func{},

              
                
                64
                +				}

              
                
                65
                +

              
                
                66
                +				if len(callArgs) != len(funArgs) {

              
                
                67
                +					return Expr{}, errors.New(fmt.Sprintf("%s(): expected %d arguments but provided %d", funName, len(funArgs), len(args)))

              
                
                68
                +				}

              
                
                69
                +

              
                
                70
                +				for index := range callArgs {

              
                
                71
                +					scope.Vars[funArgs[index].AsVar] = callArgs[index]

              
                
                72
                +				}

              
                
                73
                +

              
                
                74
                +				context.PushScope(scope)

              
                
                75
                +				for _, stmt := range args[2:] {

              
                
                76
                +					_, err := context.EvalExpr(stmt)

              
                
                77
                +					if err != nil {

              
                
                78
                +						return Expr{}, err

              
                
                79
                +					}

              
                
                80
                +				}

              
                
                81
                +				context.PopScope()

              
                
                82
                +

              
                
                83
                +				return Expr{}, nil

              
                
                84
                +			}

              
                
                85
                +

              
                
                86
                +			return Expr{}, nil

              
                
                87
                +		},

              
                
                88
                +

              
                
                89
                +		"say": func(context *EvalContext, args []Expr) (Expr, error) {

              
                
                90
                +			for _, arg := range args {

              
                
                91
                +				val, err := context.EvalExpr(arg)

              
                
                92
                +				if err != nil {

              
                
                93
                +					return Expr{}, err

              
                
                94
                +				}

              
                
                95
                +

              
                
                96
                +				switch val.Type {

              
                
                97
                +				case ExprStr:

              
                
                98
                +					fmt.Printf("%s", val.AsStr)

              
                
                99
                +

              
                
                100
                +				case ExprInt:

              
                
                101
                +					fmt.Printf("%d", val.AsInt)

              
                
                102
                +				default:

              
                
                103
                +					return Expr{}, errors.New("say() expects its arguments to be strings or numbers")

              
                
                104
                +				}

              
                
                105
                +			}

              
                
                106
                +			fmt.Printf("\n")

              
                
                107
                +			return Expr{}, nil

              
                
                108
                +		},

              
                
                109
                +

              
                
                110
                +		"http": func(context *EvalContext, args []Expr) (Expr, error) {

              
                
                111
                +			var url strings.Builder

              
                
                112
                +			for _, arg := range args {

              
                
                113
                +				val, err := context.EvalExpr(arg)

              
                
                114
                +				if err != nil {

              
                
                115
                +					return Expr{}, err

              
                
                116
                +				}

              
                
                117
                +				if val.Type != ExprStr {

              
                
                118
                +					return Expr{}, errors.New("http() expects its arguments to be strings")

              
                
                119
                +				}

              
                
                120
                +				fmt.Fprint(&url, val.AsStr)

              
                
                121
                +			}

              
                
                122
                +

              
                
                123
                +			resp, err := http.Get(url.String())

              
                
                124
                +			if err != nil {

              
                
                125
                +				return Expr{}, err

              
                
                126
                +			}

              
                
                127
                +			defer resp.Body.Close()

              
                
                128
                +

              
                
                129
                +			body, err := io.ReadAll(resp.Body)

              
                
                130
                +			if err != nil {

              
                
                131
                +				return Expr{}, err

              
                
                132
                +			}

              
                
                133
                +

              
                
                134
                +			return Expr{

              
                
                135
                +				Type:  ExprStr,

              
                
                136
                +				AsStr: string(body),

              
                
                137
                +			}, nil

              
                
                138
                +		},

              
                
                139
                +	},

              
                
                140
                +}

              
                
                141
                +

              
                
                142
                +func main() {

              
                
                143
                +	ctx := EvalContext{}

              
                
                144
                +	ctx.PushScope(escope)

              
                
                145
                +

              
                
                146
                +	if len(os.Args) < 2 {

              
                
                147
                +		fmt.Fprintf(os.Stderr, "ERROR: no input is provided\n")

              
                
                148
                +		os.Exit(1)

              
                
                149
                +	}

              
                
                150
                +

              
                
                151
                +	fpath := os.Args[1]

              
                
                152
                +	content, err := os.ReadFile(fpath)

              
                
                153
                +	if err != nil {

              
                
                154
                +		fmt.Fprintf(os.Stderr, "ERROR: could not read file %s: %s\n", fpath, err)

              
                
                155
                +		os.Exit(1)

              
                
                156
                +	}

              
                
                157
                +

              
                
                158
                +	lexer := NewLexer([]rune(string(content)), fpath)

              
                
                159
                +

              
                
                160
                +	exprs, err := ParseExprs(&lexer)

              
                
                161
                +	if err != nil {

              
                
                162
                +		fmt.Fprintln(os.Stderr, err)

              
                
                163
                +		os.Exit(1)

              
                
                164
                +	}

              
                
                165
                +

              
                
                166
                +	for _, expr := range exprs {

              
                
                167
                +		if _, err := ctx.EvalExpr(expr); err != nil {

              
                
                168
                +			fmt.Fprintln(os.Stderr, err)

              
                
                169
                +			os.Exit(1)

              
                
                170
                +		}

              
                
                171
                +	}

              
                
                172
                +}

              
A blo/go.mod
···
                
                1
                +module blo

              
                
                2
                +

              
                
                3
                +go 1.26.1

              
A blo/test.blo
···
                
                1
                +let(myVar, "value123")

              
                
                2
                +say("Value of just declared var is ", myVar, ";")

              
                
                3
                +

              
                
                4
                +define(hello, args(name, city),

              
                
                5
                +  say("Hello, ", name, " from ", city, "!"),

              
                
                6
                +  say("Your weather is ", http("https://wttr.in/", city, "?format=4")))

              
                
                7
                +

              
                
                8
                +hello(my_name, "Kyiv")