all repos

scratch @ 6fd1757

⭐ 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
Authored at: 2026-04-10 21:21:20 +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")