diff --git a/.gitignore b/.gitignore index 6f72f89..8fd2b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ go.work.sum # env file .env + +.DS_Store \ No newline at end of file diff --git a/TODO.md b/TODO.md index 82c2f77..ee01a98 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,5 @@ # TODO (sometimes i forget to update it) -# Fix Date [IN PROGESS] -- Build in Functions are not Registered - ## Fix compilation to executable ## Add Interfaces diff --git a/cmd/compiler.go b/cmd/compiler.go index 8c7d7cf..5b223cd 100644 --- a/cmd/compiler.go +++ b/cmd/compiler.go @@ -11,7 +11,6 @@ import ( "github.com/burnlang/burn/pkg/ast" "github.com/burnlang/burn/pkg/lexer" "github.com/burnlang/burn/pkg/parser" - "github.com/burnlang/burn/pkg/stdlib" "github.com/burnlang/burn/pkg/typechecker" ) @@ -88,24 +87,6 @@ func createExecutableWrapper(goFilePath, burnFilePath, burnSource string) error return err } - // Ensure all standard library files are included - for name, content := range stdlib.StdLibFiles { - stdlibPath := "src/lib/std/" + name + ".bn" - if _, exists := imports[stdlibPath]; !exists { - imports[stdlibPath] = content - } - - if _, exists := imports[name]; !exists { - imports[name] = content - } - - // Also include with std/ prefix - stdPrefix := "std/" + name - if _, exists := imports[stdPrefix]; !exists { - imports[stdPrefix] = content - } - } - wrapperTemplate := `package main import ( @@ -117,85 +98,56 @@ import ( "github.com/burnlang/burn/pkg/interpreter" "github.com/burnlang/burn/pkg/lexer" "github.com/burnlang/burn/pkg/parser" - "github.com/burnlang/burn/pkg/stdlib" - "github.com/burnlang/burn/pkg/typechecker" ) - -var mainSource = %q - - -var importSources = map[string]string{ -%s -} - func main() { - exitCode := runBurnProgram() - os.Exit(exitCode) -} + mainSource := %s -func runBurnProgram() int { - // Create a new interpreter and ensure all built-ins are registered - interp := interpreter.New() - - // Register standard libraries first - interp.RegisterBuiltinStandardLibraries() - - // Register all imports - for path, source := range importSources { - if err := registerImport(interp, path, source); err != nil { - fmt.Fprintf(os.Stderr, "Import error for %%s: %%v\n", path, err) - // Continue with other imports instead of failing - } + imports := map[string]string{ +%s } - // Parse and interpret the main source - lex := lexer.New(mainSource) - tokens, err := lex.Tokenize() + l := lexer.New(mainSource) + tokens, err := l.Tokenize() if err != nil { fmt.Fprintf(os.Stderr, "Lexical error: %%v\n", err) - return 1 + os.Exit(1) } p := parser.New(tokens) program, err := p.Parse() if err != nil { fmt.Fprintf(os.Stderr, "Parse error: %%v\n", err) - return 1 + os.Exit(1) } - tc := typechecker.New() - if err := tc.Check(program.Declarations); err != nil { - fmt.Fprintf(os.Stderr, "Type error: %%v\n", err) - return 1 + i := interpreter.New() + + + for importPath, importSource := range imports { + if err := processImport(i, importPath, importSource); err != nil { + fmt.Fprintf(os.Stderr, "Import error for '%%s': %%v\n", importPath, err) + os.Exit(1) + } } - _, err = interp.Interpret(program) + _, err = i.Interpret(program) if err != nil { fmt.Fprintf(os.Stderr, "Runtime error: %%v\n", err) - return 1 + os.Exit(1) } - - return 0 } -func registerImport(interp *interpreter.Interpreter, path, source string) error { - // Handle special standard libraries - basename := filepath.Base(path) - if strings.HasSuffix(basename, ".bn") { - basename = strings.TrimSuffix(basename, ".bn") - } - - // Register built-in standard libraries directly - if basename == "date" || basename == "http" || basename == "time" || - path == "std/date" || path == "std/http" || path == "std/time" { - // These are already registered in RegisterBuiltinStandardLibraries +func processImport(i *interpreter.Interpreter, importPath, importSource string) error { + + if strings.HasPrefix(importPath, "std/") || + (!strings.Contains(importPath, "/") && !strings.Contains(importPath, "\\") && + (importPath == "date" || importPath == "http" || importPath == "time")) { return nil } - // For other imports, parse and interpret them - lex := lexer.New(source) - tokens, err := lex.Tokenize() + l := lexer.New(importSource) + tokens, err := l.Tokenize() if err != nil { return err } @@ -205,44 +157,47 @@ func registerImport(interp *interpreter.Interpreter, path, source string) error if err != nil { return err } - - // Create a new interpreter for the import - importInterp := interpreter.New() - importInterp.RegisterBuiltinStandardLibraries() - - // Interpret the import - _, err = importInterp.Interpret(program) + + importInterpreter := interpreter.New() + + _, err = importInterpreter.Interpret(program) if err != nil { return err } + - // Copy functions (except main) from the import to the main interpreter - for name, fn := range importInterp.GetFunctions() { + for name, fn := range importInterpreter.GetFunctions() { if name != "main" { - interp.AddFunction(name, fn) + i.AddFunction(name, fn) } } - - // Also copy environment values to ensure builtins are available - for name, val := range importInterp.GetVariables() { - if name != "main" && val != nil { - // Don't overwrite existing values - interp.AddVariable(name, val) - } + + for name, value := range importInterpreter.GetVariables() { + i.AddVariable(name, value) } return nil } ` - var importSourcesContent strings.Builder - for path, source := range imports { - importSourcesContent.WriteString(fmt.Sprintf("\t%q: %q,\n", path, source)) + var importStrings []string + for name, content := range imports { + + if strings.HasPrefix(name, "std/") || + (!strings.Contains(name, "/") && !strings.Contains(name, "\\") && + (name == "date" || name == "http" || name == "time")) { + continue + } + + escapedContent := strings.ReplaceAll(content, "`", "` + \"`\" + `") + importStrings = append(importStrings, fmt.Sprintf(" %q: `%s`,", name, escapedContent)) } - wrapperCode := fmt.Sprintf(wrapperTemplate, burnSource, importSourcesContent.String()) + escapedSource := strings.ReplaceAll(burnSource, "`", "` + \"`\" + `") - return os.WriteFile(goFilePath, []byte(wrapperCode), 0644) + finalCode := fmt.Sprintf(wrapperTemplate, fmt.Sprintf("`%s`", escapedSource), strings.Join(importStrings, "\n")) + + return os.WriteFile(goFilePath, []byte(finalCode), 0644) } func collectImports(mainFile, mainSource string) (map[string]string, error) { @@ -253,30 +208,6 @@ func collectImports(mainFile, mainSource string) (map[string]string, error) { return nil, fmt.Errorf("error getting current directory: %v", err) } - // Register all standard libraries first - for name, content := range stdlib.StdLibFiles { - imports[name] = content - imports["std/"+name] = content - imports["std/"+name+".bn"] = content - fmt.Printf("Including standard library %s (built-in)\n", name) - } - - // Check for standard libraries in the file system - stdLibDir := filepath.Join(filepath.Dir(mainFile), "src", "lib", "std") - if _, err := os.Stat(stdLibDir); err == nil { - err = stdlib.AutoRegisterLibrariesFromDir(stdLibDir) - if err == nil { - for name, content := range stdlib.StdLibFiles { - if _, exists := imports[name]; !exists { - imports[name] = content - imports["std/"+name] = content - imports["std/"+name+".bn"] = content - fmt.Printf("Auto-discovered standard library %s\n", name) - } - } - } - } - lex := lexer.New(mainSource) tokens, err := lex.Tokenize() if err != nil { @@ -292,49 +223,25 @@ func collectImports(mainFile, mainSource string) (map[string]string, error) { baseDir := filepath.Dir(mainFile) processImport := func(imp *ast.ImportDeclaration) error { - // Check if it's a standard library first - if strings.HasPrefix(imp.Path, "std/") { - libName := strings.TrimPrefix(imp.Path, "std/") - libName = strings.TrimSuffix(libName, ".bn") - if content, exists := stdlib.StdLibFiles[libName]; exists { - imports[imp.Path] = content - return nil - } - } - - // Check if it's a direct standard library reference - moduleName := imp.Path - if strings.HasSuffix(moduleName, ".bn") { - moduleName = strings.TrimSuffix(moduleName, ".bn") - } - baseName := filepath.Base(moduleName) - if content, exists := stdlib.StdLibFiles[baseName]; exists { - imports[imp.Path] = content + if strings.HasPrefix(imp.Path, "std/") || + (!strings.Contains(imp.Path, "/") && !strings.Contains(imp.Path, "\\") && + (imp.Path == "date" || imp.Path == "http" || imp.Path == "time")) { return nil } - // Try to find the file var fileContent []byte var readErr error - // Try direct path first - fileContent, readErr = os.ReadFile(imp.Path) - if readErr == nil { - imports[imp.Path] = string(fileContent) - fmt.Printf("Including imported file %s\n", imp.Path) - return collectNestedImports(imp.Path, string(fileContent), imports, workingDir, baseDir) - } - - // Try multiple possible paths possiblePaths := []string{ + imp.Path, filepath.Join(baseDir, imp.Path), + filepath.Join(workingDir, imp.Path), imp.Path + ".bn", filepath.Join(baseDir, imp.Path+".bn"), - filepath.Join(baseDir, "src", "lib", imp.Path), - filepath.Join(baseDir, "src", "lib", imp.Path+".bn"), - filepath.Join(baseDir, "src", "lib", "std", imp.Path), - filepath.Join(baseDir, "src", "lib", "std", imp.Path+".bn"), + filepath.Join(workingDir, imp.Path+".bn"), + filepath.Join(baseDir, "test", imp.Path), + filepath.Join(baseDir, "test", imp.Path+".bn"), } for _, path := range possiblePaths { @@ -346,12 +253,6 @@ func collectImports(mainFile, mainSource string) (map[string]string, error) { } } - // If we get here and it's a std/ import, don't error - it might be handled elsewhere - if strings.HasPrefix(imp.Path, "std/") { - fmt.Printf("Warning: Could not find standard library file for %s, using built-in if available\n", imp.Path) - return nil - } - return fmt.Errorf("could not find import '%s'", imp.Path) } @@ -393,25 +294,9 @@ func collectNestedImports(filePath, source string, imports map[string]string, wo return nil } - // Check if it's a standard library first - if strings.HasPrefix(imp.Path, "std/") { - libName := strings.TrimPrefix(imp.Path, "std/") - libName = strings.TrimSuffix(libName, ".bn") - if content, exists := stdlib.StdLibFiles[libName]; exists { - imports[imp.Path] = content - fmt.Printf("Including standard library %s (built-in)\n", libName) - return nil - } - } - - baseName := filepath.Base(imp.Path) - if strings.HasSuffix(baseName, ".bn") { - baseName = strings.TrimSuffix(baseName, ".bn") - } - - if stdLib, exists := stdlib.StdLibFiles[baseName]; exists { - imports[imp.Path] = stdLib - fmt.Printf("Including standard library %s (built-in)\n", baseName) + if strings.HasPrefix(imp.Path, "std/") || + (!strings.Contains(imp.Path, "/") && !strings.Contains(imp.Path, "\\") && + (imp.Path == "date" || imp.Path == "http" || imp.Path == "time")) { return nil } @@ -435,12 +320,6 @@ func collectNestedImports(filePath, source string, imports map[string]string, wo } } - // If we get here and it's a std/ import, don't error - it might be handled elsewhere - if strings.HasPrefix(imp.Path, "std/") { - fmt.Printf("Warning: Could not find standard library file for %s, using built-in if available\n", imp.Path) - return nil - } - return fmt.Errorf("could not find nested import '%s'", imp.Path) } diff --git a/cmd/executor.go b/cmd/executor.go index 3edb5f1..0037bb3 100644 --- a/cmd/executor.go +++ b/cmd/executor.go @@ -84,6 +84,7 @@ func execute(source string, debug bool, stdout io.Writer) (interface{}, error) { } interpreter := interpreter.New() + interpreter.SetStdout(stdout) result, err := interpreter.Interpret(program) if err != nil { return nil, formattedError("Runtime error", err, source, interpreter.Position()) diff --git a/cmd/utils.go b/cmd/utils.go index 6d207eb..06168e1 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -61,6 +61,8 @@ func tokenTypeToString(tokenType lexer.TokenType) string { return "CONST" case lexer.TokenTypeKeyword: return "DEF" + case lexer.TokenAs: + return "AS" default: return fmt.Sprintf("TOKEN(%d)", int(tokenType)) } diff --git a/pkg/ast/declaration.go b/pkg/ast/declaration.go index 6d51a92..b4e8c1d 100644 --- a/pkg/ast/declaration.go +++ b/pkg/ast/declaration.go @@ -75,6 +75,7 @@ func (v *VariableDeclaration) String() string { type ImportDeclaration struct { Path string + Alias string Position int } diff --git a/pkg/interpreter/builtins.go b/pkg/interpreter/builtins.go index d1207b2..cc19be2 100644 --- a/pkg/interpreter/builtins.go +++ b/pkg/interpreter/builtins.go @@ -6,14 +6,14 @@ import ( "os" "strconv" "strings" - "time" ) type Value interface{} type BuiltinFunction struct { - Name string - Fn func(args []Value) (Value, error) + Name string + Fn func(args []Value) (Value, error) + Caller func(args []Value) (Value, error) } func (b *BuiltinFunction) Call(args []Value) (Value, error) { @@ -21,11 +21,41 @@ func (b *BuiltinFunction) Call(args []Value) (Value, error) { } func (i *Interpreter) addBuiltins() { + i.environment["print"] = &BuiltinFunction{ Name: "print", Fn: func(args []Value) (Value, error) { - for _, arg := range args { - fmt.Println(arg) + for idx, arg := range args { + if idx > 0 { + fmt.Print(" ") + } + if i.stdout != nil { + fmt.Fprint(i.stdout, arg) + } else { + fmt.Print(arg) + } + } + return nil, nil + }, + } + + i.environment["println"] = &BuiltinFunction{ + Name: "println", + Fn: func(args []Value) (Value, error) { + for idx, arg := range args { + if idx > 0 { + fmt.Print(" ") + } + if i.stdout != nil { + fmt.Fprint(i.stdout, arg) + } else { + fmt.Print(arg) + } + } + if i.stdout != nil { + fmt.Fprintln(i.stdout) + } else { + fmt.Println() } return nil, nil }, @@ -50,25 +80,26 @@ func (i *Interpreter) addBuiltins() { Name: "toString", Fn: func(args []Value) (Value, error) { if len(args) != 1 { - return nil, fmt.Errorf("toString expects exactly one argument") + return nil, fmt.Errorf("toString expects 1 argument, got %d", len(args)) } - switch val := args[0].(type) { + arg := args[0] + switch v := arg.(type) { + case string: + return v, nil + case int: + return fmt.Sprintf("%d", v), nil case float64: - if val == float64(int(val)) { - return fmt.Sprintf("%.0f", val), nil + if v == float64(int(v)) { + return fmt.Sprintf("%.0f", v), nil } - return fmt.Sprintf("%g", val), nil - case int: - return fmt.Sprintf("%d", val), nil - case string: - return val, nil + return fmt.Sprintf("%g", v), nil case bool: - return fmt.Sprintf("%t", val), nil + return fmt.Sprintf("%t", v), nil case nil: return "null", nil default: - return fmt.Sprintf("%v", val), nil + return fmt.Sprintf("%v", v), nil } }, } @@ -135,16 +166,6 @@ func (i *Interpreter) addBuiltins() { }, } - i.environment["now"] = &BuiltinFunction{ - Name: "now", - Fn: func(args []Value) (Value, error) { - if len(args) != 0 { - return nil, fmt.Errorf("now expects no arguments") - } - currentTime := float64(time.Now().UnixNano()) / 1e9 - return currentTime, nil - }, - } i.registerDateLibrary() i.registerHTTPLibrary() i.registerTimeLibrary() diff --git a/pkg/interpreter/date.go b/pkg/interpreter/date.go new file mode 100644 index 0000000..93f9d8b --- /dev/null +++ b/pkg/interpreter/date.go @@ -0,0 +1,319 @@ +package interpreter + +import ( + "fmt" + "time" + + "github.com/burnlang/burn/pkg/ast" +) + +func (i *Interpreter) registerDateLibrary() { + + i.types["Date"] = &ast.TypeDefinition{ + Name: "Date", + Fields: []ast.TypeField{ + {Name: "year", Type: "int"}, + {Name: "month", Type: "int"}, + {Name: "day", Type: "int"}, + }, + } + + dateClass := NewClass("Date") + + dateClass.AddStatic("now", &ast.FunctionDeclaration{ + Name: "now", + Parameters: []ast.Parameter{}, + ReturnType: "Date", + }) + + dateClass.AddStatic("formatDate", &ast.FunctionDeclaration{ + Name: "formatDate", + Parameters: []ast.Parameter{{Name: "date", Type: "Date"}}, + ReturnType: "string", + }) + + dateClass.AddStatic("currentYear", &ast.FunctionDeclaration{ + Name: "currentYear", + Parameters: []ast.Parameter{}, + ReturnType: "int", + }) + + dateClass.AddStatic("currentMonth", &ast.FunctionDeclaration{ + Name: "currentMonth", + Parameters: []ast.Parameter{}, + ReturnType: "int", + }) + + dateClass.AddStatic("currentDay", &ast.FunctionDeclaration{ + Name: "currentDay", + Parameters: []ast.Parameter{}, + ReturnType: "int", + }) + + dateClass.AddStatic("isLeapYear", &ast.FunctionDeclaration{ + Name: "isLeapYear", + Parameters: []ast.Parameter{{Name: "year", Type: "int"}}, + ReturnType: "bool", + }) + + dateClass.AddStatic("daysInMonth", &ast.FunctionDeclaration{ + Name: "daysInMonth", + Parameters: []ast.Parameter{{Name: "year", Type: "int"}, {Name: "month", Type: "int"}}, + ReturnType: "int", + }) + + dateClass.AddStatic("createDate", &ast.FunctionDeclaration{ + Name: "createDate", + Parameters: []ast.Parameter{{Name: "year", Type: "int"}, {Name: "month", Type: "int"}, {Name: "day", Type: "int"}}, + ReturnType: "Date", + }) + + dateClass.AddStatic("dayOfWeek", &ast.FunctionDeclaration{ + Name: "dayOfWeek", + Parameters: []ast.Parameter{{Name: "date", Type: "Date"}}, + ReturnType: "int", + }) + + dateClass.AddStatic("addDays", &ast.FunctionDeclaration{ + Name: "addDays", + Parameters: []ast.Parameter{{Name: "date", Type: "Date"}, {Name: "days", Type: "int"}}, + ReturnType: "Date", + }) + + dateClass.AddStatic("subtractDays", &ast.FunctionDeclaration{ + Name: "subtractDays", + Parameters: []ast.Parameter{{Name: "date", Type: "Date"}, {Name: "days", Type: "int"}}, + ReturnType: "Date", + }) + + dateClass.AddStatic("today", &ast.FunctionDeclaration{ + Name: "today", + Parameters: []ast.Parameter{}, + ReturnType: "string", + }) + + i.classes["Date"] = dateClass + i.environment["Date"] = dateClass + + i.environment["Date.now"] = &BuiltinFunction{ + Name: "Date.now", + Fn: func(args []Value) (Value, error) { + now := time.Now() + return map[string]interface{}{ + "year": float64(now.Year()), + "month": float64(int(now.Month())), + "day": float64(now.Day()), + }, nil + }, + } + + i.environment["Date.formatDate"] = &BuiltinFunction{ + Name: "Date.formatDate", + Fn: func(args []Value) (Value, error) { + if len(args) != 1 { + return nil, fmt.Errorf("Date.formatDate expects exactly one Date argument") + } + + dateMap, ok := args[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("Date.formatDate expects a Date argument") + } + + year, _ := dateMap["year"].(float64) + month, _ := dateMap["month"].(float64) + day, _ := dateMap["day"].(float64) + + return fmt.Sprintf("%04.0f-%02.0f-%02.0f", year, month, day), nil + }, + } + + i.environment["Date.currentYear"] = &BuiltinFunction{ + Name: "Date.currentYear", + Fn: func(args []Value) (Value, error) { + return float64(time.Now().Year()), nil + }, + } + + i.environment["Date.currentMonth"] = &BuiltinFunction{ + Name: "Date.currentMonth", + Fn: func(args []Value) (Value, error) { + return float64(int(time.Now().Month())), nil + }, + } + + i.environment["Date.currentDay"] = &BuiltinFunction{ + Name: "Date.currentDay", + Fn: func(args []Value) (Value, error) { + return float64(time.Now().Day()), nil + }, + } + + i.environment["Date.isLeapYear"] = &BuiltinFunction{ + Name: "Date.isLeapYear", + Fn: func(args []Value) (Value, error) { + if len(args) != 1 { + return nil, fmt.Errorf("Date.isLeapYear expects exactly one numeric argument (year)") + } + + year, ok := args[0].(float64) + if !ok { + return nil, fmt.Errorf("Date.isLeapYear expects a numeric year") + } + + y := int(year) + return (y%4 == 0 && y%100 != 0) || (y%400 == 0), nil + }, + } + + i.environment["Date.daysInMonth"] = &BuiltinFunction{ + Name: "Date.daysInMonth", + Fn: func(args []Value) (Value, error) { + if len(args) != 2 { + return nil, fmt.Errorf("Date.daysInMonth expects exactly two numeric arguments (year, month)") + } + + year, ok1 := args[0].(float64) + month, ok2 := args[1].(float64) + if !ok1 || !ok2 { + return nil, fmt.Errorf("Date.daysInMonth expects numeric year and month") + } + + t := time.Date(int(year), time.Month(int(month)), 32, 0, 0, 0, 0, time.UTC) + return float64(32 - t.Day()), nil + }, + } + + i.environment["Date.createDate"] = &BuiltinFunction{ + Name: "Date.createDate", + Fn: func(args []Value) (Value, error) { + if len(args) != 3 { + return nil, fmt.Errorf("Date.createDate expects exactly three numeric arguments (year, month, day)") + } + + year, ok1 := args[0].(float64) + month, ok2 := args[1].(float64) + day, ok3 := args[2].(float64) + if !ok1 || !ok2 || !ok3 { + return nil, fmt.Errorf("Date.createDate expects numeric year, month, and day") + } + + return map[string]interface{}{ + "year": year, + "month": month, + "day": day, + }, nil + }, + } + + i.environment["Date.dayOfWeek"] = &BuiltinFunction{ + Name: "Date.dayOfWeek", + Fn: func(args []Value) (Value, error) { + if len(args) != 1 { + return nil, fmt.Errorf("Date.dayOfWeek expects exactly one Date argument") + } + + dateMap, ok := args[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("Date.dayOfWeek expects a Date argument") + } + + year, _ := dateMap["year"].(float64) + month, _ := dateMap["month"].(float64) + day, _ := dateMap["day"].(float64) + + t := time.Date(int(year), time.Month(int(month)), int(day), 0, 0, 0, 0, time.UTC) + return float64((int(t.Weekday()) + 1) % 7), nil + }, + } + + i.environment["Date.addDays"] = &BuiltinFunction{ + Name: "Date.addDays", + Fn: func(args []Value) (Value, error) { + if len(args) != 2 { + return nil, fmt.Errorf("Date.addDays expects exactly two arguments (date, days)") + } + + dateMap, ok := args[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("Date.addDays expects a Date as first argument") + } + + days, ok := args[1].(float64) + if !ok { + return nil, fmt.Errorf("Date.addDays expects a numeric days value") + } + + year, _ := dateMap["year"].(float64) + month, _ := dateMap["month"].(float64) + day, _ := dateMap["day"].(float64) + + t := time.Date(int(year), time.Month(int(month)), int(day), 0, 0, 0, 0, time.UTC) + newTime := t.AddDate(0, 0, int(days)) + + return map[string]interface{}{ + "year": float64(newTime.Year()), + "month": float64(int(newTime.Month())), + "day": float64(newTime.Day()), + }, nil + }, + } + + i.environment["Date.subtractDays"] = &BuiltinFunction{ + Name: "Date.subtractDays", + Fn: func(args []Value) (Value, error) { + if len(args) != 2 { + return nil, fmt.Errorf("Date.subtractDays expects exactly two arguments (date, days)") + } + + dateMap, ok := args[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("Date.subtractDays expects a Date as first argument") + } + + days, ok := args[1].(float64) + if !ok { + return nil, fmt.Errorf("Date.subtractDays expects a numeric days value") + } + + year, _ := dateMap["year"].(float64) + month, _ := dateMap["month"].(float64) + day, _ := dateMap["day"].(float64) + + t := time.Date(int(year), time.Month(int(month)), int(day), 0, 0, 0, 0, time.UTC) + newTime := t.AddDate(0, 0, -int(days)) + + return map[string]interface{}{ + "year": float64(newTime.Year()), + "month": float64(int(newTime.Month())), + "day": float64(newTime.Day()), + }, nil + }, + } + + i.environment["Date.today"] = &BuiltinFunction{ + Name: "Date.today", + Fn: func(args []Value) (Value, error) { + now := time.Now() + return fmt.Sprintf("%04d-%02d-%02d", now.Year(), int(now.Month()), now.Day()), nil + }, + } + + aliases := map[string]string{ + "now": "Date.now", + "formatDate": "Date.formatDate", + "currentYear": "Date.currentYear", + "currentMonth": "Date.currentMonth", + "currentDay": "Date.currentDay", + "isLeapYear": "Date.isLeapYear", + "daysInMonth": "Date.daysInMonth", + "createDate": "Date.createDate", + "dayOfWeek": "Date.dayOfWeek", + "addDays": "Date.addDays", + "subtractDays": "Date.subtractDays", + "today": "Date.today", + } + + for oldName, newName := range aliases { + i.environment[oldName] = i.environment[newName] + } +} diff --git a/pkg/interpreter/expression.go b/pkg/interpreter/expression.go index dc89a8b..041af80 100644 --- a/pkg/interpreter/expression.go +++ b/pkg/interpreter/expression.go @@ -32,6 +32,8 @@ func (i *Interpreter) evaluateExpression(expr ast.Expression) (Value, error) { return value, nil case *ast.CallExpression: return i.evaluateCall(e) + case *ast.ClassMethodCallExpression: + return i.evaluateClassMethodCall(e) case *ast.GetExpression: object, err := i.evaluateExpression(e.Object) if err != nil { @@ -295,144 +297,86 @@ func (i *Interpreter) evaluateUnary(expr *ast.UnaryExpression) (Value, error) { } func (i *Interpreter) evaluateCall(expr *ast.CallExpression) (Value, error) { - if getExpr, ok := expr.Callee.(*ast.GetExpression); ok { - if classNameExpr, ok := getExpr.Object.(*ast.VariableExpression); ok { - className := classNameExpr.Name - methodName := getExpr.Name - class, exists := i.classes[className] - if !exists { - return nil, fmt.Errorf("undefined class: %s", className) - } + if varExpr, ok := expr.Callee.(*ast.VariableExpression); ok { - args := make([]Value, 0, len(expr.Arguments)) - for _, arg := range expr.Arguments { - value, err := i.evaluateExpression(arg) - if err != nil { - return nil, err - } - args = append(args, value) - } + if builtinFunc, exists := i.environment[varExpr.Name]; exists { + if bf, ok := builtinFunc.(*BuiltinFunction); ok { - if static, exists := class.Statics[methodName]; exists { - result, err := i.executeFunction(static, args) - if err != nil { - return nil, err - } - - if methodName == "create" { - if mapResult, ok := result.(map[string]interface{}); ok { - - return &Struct{ - TypeName: className, - Fields: mapResult, - }, nil + var args []Value + for _, arg := range expr.Arguments { + val, err := i.evaluateExpression(arg) + if err != nil { + return nil, err } + args = append(args, val) } - return result, nil + + return bf.Fn(args) } + } + + if function, exists := i.functions[varExpr.Name]; exists { - if instanceMethod, exists := class.Methods[methodName]; exists { - result, err := i.executeFunction(instanceMethod, args) + var args []Value + for _, arg := range expr.Arguments { + val, err := i.evaluateExpression(arg) if err != nil { return nil, err } - - if methodName == "create" { - if mapResult, ok := result.(map[string]interface{}); ok { - - return &Struct{ - TypeName: className, - Fields: mapResult, - }, nil - } - } - return result, nil + args = append(args, val) } - builtinFuncName := fmt.Sprintf("%s.%s", className, methodName) - if builtinFunc, exists := i.environment[builtinFuncName]; exists { - if bf, ok := builtinFunc.(*BuiltinFunction); ok { - result, err := bf.Call(args) - if err != nil { - return nil, err - } + oldEnv := i.environment + i.environment = make(map[string]Value) - if methodName == "create" { - if mapResult, ok := result.(map[string]interface{}); ok { + for k, v := range oldEnv { + i.environment[k] = v + } - return &Struct{ - TypeName: className, - Fields: mapResult, - }, nil - } - } - return result, nil + for j, param := range function.Parameters { + if j < len(args) { + i.environment[param.Name] = args[j] } } - return nil, fmt.Errorf("undefined static method '%s' in class '%s'", methodName, className) - } - - object, err := i.evaluateExpression(getExpr.Object) - if err != nil { - return nil, err - } - - if structObj, ok := object.(*Struct); ok { - methodName := getExpr.Name - - args := make([]Value, len(expr.Arguments)) - for j, arg := range expr.Arguments { - val, err := i.evaluateExpression(arg) + var result Value + for _, decl := range function.Body { + val, err := i.executeDeclaration(decl) if err != nil { + i.environment = oldEnv return nil, err } - args[j] = val - } - - if class, exists := i.classes[structObj.TypeName]; exists { - allArgs := make([]Value, len(args)+1) - allArgs[0] = structObj - copy(allArgs[1:], args) - - if method, exists := class.Methods[methodName]; exists { - return i.executeFunction(method, allArgs) + if val != nil { + result = val } } - return nil, fmt.Errorf("undefined method '%s' on type '%s'", methodName, structObj.TypeName) - } - - return nil, fmt.Errorf("cannot call method on expression of type %T", object) - } - - callee, ok := expr.Callee.(*ast.VariableExpression) - if !ok { - return nil, fmt.Errorf("callee is not a function name") - } + i.environment = oldEnv - args := make([]Value, 0, len(expr.Arguments)) - for _, arg := range expr.Arguments { - value, err := i.evaluateExpression(arg) - if err != nil { - return nil, err + return result, nil } - args = append(args, value) } - if builtinFunc, exists := i.environment[callee.Name]; exists { - if bf, ok := builtinFunc.(*BuiltinFunction); ok { - return bf.Call(args) - } - } + if getExpr, ok := expr.Callee.(*ast.GetExpression); ok { + if varExpr, ok := getExpr.Object.(*ast.VariableExpression); ok { + if class, exists := i.classes[varExpr.Name]; exists { + var args []Value + for _, arg := range expr.Arguments { + val, err := i.evaluateExpression(arg) + if err != nil { + return nil, err + } + args = append(args, val) + } - fn, exists := i.functions[callee.Name] - if !exists { - return nil, fmt.Errorf("undefined function: %s", callee.Name) + return class.Call(getExpr.Name, i, args) + } + } + return i.evaluateExpression(getExpr) } - return i.executeFunction(fn, args) + return nil, fmt.Errorf("function not found: %v", expr.Callee) } func (i *Interpreter) evaluateLiteral(expr *ast.LiteralExpression) (Value, error) { diff --git a/pkg/interpreter/stdlib_http.go b/pkg/interpreter/http.go similarity index 82% rename from pkg/interpreter/stdlib_http.go rename to pkg/interpreter/http.go index ad54a08..756fcee 100644 --- a/pkg/interpreter/stdlib_http.go +++ b/pkg/interpreter/http.go @@ -17,7 +17,7 @@ var httpHeaders = map[string]string{ } func (i *Interpreter) registerHTTPLibrary() { - + i.types["HTTPResponse"] = &ast.TypeDefinition{ Name: "HTTPResponse", Fields: []ast.TypeField{ @@ -68,7 +68,6 @@ func (i *Interpreter) registerHTTPLibrary() { i.classes["HTTP"] = httpClass i.environment["HTTP"] = httpClass - i.environment["HTTP.get"] = &BuiltinFunction{ Name: "HTTP.get", Fn: i.httpGet, @@ -98,7 +97,6 @@ func (i *Interpreter) registerHTTPLibrary() { Fn: i.httpSetHeaders, } - i.environment["get"] = i.environment["HTTP.get"] i.environment["post"] = i.environment["HTTP.post"] i.environment["put"] = i.environment["HTTP.put"] @@ -145,13 +143,10 @@ func (i *Interpreter) httpGet(args []Value) (Value, error) { } } - return &Struct{ - TypeName: "HTTPResponse", - Fields: map[string]interface{}{ - "statusCode": resp.StatusCode, - "body": string(body), - "headers": headers, - }, + return map[string]interface{}{ + "statusCode": float64(resp.StatusCode), + "body": string(body), + "headers": headers, }, nil } @@ -196,13 +191,10 @@ func (i *Interpreter) httpPost(args []Value) (Value, error) { } } - return &Struct{ - TypeName: "HTTPResponse", - Fields: map[string]interface{}{ - "statusCode": resp.StatusCode, - "body": string(body), - "headers": headers, - }, + return map[string]interface{}{ + "statusCode": float64(resp.StatusCode), + "body": string(body), + "headers": headers, }, nil } @@ -247,13 +239,10 @@ func (i *Interpreter) httpPut(args []Value) (Value, error) { } } - return &Struct{ - TypeName: "HTTPResponse", - Fields: map[string]interface{}{ - "statusCode": resp.StatusCode, - "body": string(body), - "headers": headers, - }, + return map[string]interface{}{ + "statusCode": float64(resp.StatusCode), + "body": string(body), + "headers": headers, }, nil } @@ -294,13 +283,10 @@ func (i *Interpreter) httpDelete(args []Value) (Value, error) { } } - return &Struct{ - TypeName: "HTTPResponse", - Fields: map[string]interface{}{ - "statusCode": resp.StatusCode, - "body": string(body), - "headers": headers, - }, + return map[string]interface{}{ + "statusCode": float64(resp.StatusCode), + "body": string(body), + "headers": headers, }, nil } @@ -308,64 +294,69 @@ func (i *Interpreter) httpSetHeaders(args []Value) (Value, error) { if len(args) != 1 { return nil, fmt.Errorf("HTTP.setHeaders expects exactly one array argument") } - headerArray, ok := args[0].([]Value) + + headers, ok := args[0].([]Value) if !ok { - return nil, fmt.Errorf("HTTP.setHeaders expects an array of header strings") + return nil, fmt.Errorf("HTTP.setHeaders expects an array of strings") } - newHeaders := make(map[string]string) - for _, hv := range headerArray { - headerStr, ok := hv.(string) + httpHeaders = make(map[string]string) + + for _, header := range headers { + headerStr, ok := header.(string) if !ok { - return nil, fmt.Errorf("each header must be a string") + return nil, fmt.Errorf("HTTP.setHeaders expects an array of strings") } + parts := strings.SplitN(headerStr, ":", 2) if len(parts) != 2 { - return nil, fmt.Errorf("invalid header format: %s", headerStr) + return nil, fmt.Errorf("Invalid header format: %s", headerStr) } - name := strings.TrimSpace(parts[0]) + + key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) - newHeaders[name] = value + httpHeaders[key] = value } - httpHeaders = newHeaders return true, nil } func (i *Interpreter) httpGetHeader(args []Value) (Value, error) { if len(args) != 2 { - return nil, fmt.Errorf("HTTP.getHeader expects exactly two arguments") + return nil, fmt.Errorf("HTTP.getHeader expects exactly two arguments (response, headerName)") } - respObj, ok := args[0].(*Struct) - if !ok || respObj.TypeName != "HTTPResponse" { + + response, ok := args[0].(map[string]interface{}) + if !ok { return nil, fmt.Errorf("HTTP.getHeader expects an HTTPResponse as first argument") } + headerName, ok := args[1].(string) if !ok { - return nil, fmt.Errorf("HTTP.getHeader expects a string header name") + return nil, fmt.Errorf("HTTP.getHeader expects a string header name as second argument") } - headers, ok := respObj.Fields["headers"].([]Value) + headers, ok := response["headers"].([]Value) if !ok { return "", nil } - headerName = strings.ToLower(headerName) - for _, h := range headers { - headerStr, ok := h.(string) + for _, header := range headers { + headerStr, ok := header.(string) if !ok { continue } + parts := strings.SplitN(headerStr, ":", 2) - if len(parts) != 2 { - continue - } - name := strings.ToLower(strings.TrimSpace(parts[0])) - value := strings.TrimSpace(parts[1]) - if name == headerName { - return value, nil + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + if strings.ToLower(key) == strings.ToLower(headerName) { + return value, nil + } } } + return "", nil } @@ -389,29 +380,26 @@ func (i *Interpreter) httpParseJSON(args []Value) (Value, error) { func convertJSONToBurn(value interface{}) Value { switch v := value.(type) { - case map[string]interface{}: - fields := make(map[string]interface{}) - for key, val := range v { - fields[key] = convertJSONToBurn(val) - } - return &Struct{ - TypeName: "Object", - Fields: fields, - } - case []interface{}: - array := make([]Value, len(v)) - for i, val := range v { - array[i] = convertJSONToBurn(val) - } - return array - case string: + case nil: + return nil + case bool: return v case float64: return v - case bool: + case string: return v - case nil: - return nil + case []interface{}: + arr := make([]Value, len(v)) + for i, item := range v { + arr[i] = convertJSONToBurn(item) + } + return arr + case map[string]interface{}: + obj := make(map[string]interface{}) + for key, val := range v { + obj[key] = convertJSONToBurn(val) + } + return obj default: return fmt.Sprintf("%v", v) } diff --git a/pkg/interpreter/interpreter.go b/pkg/interpreter/interpreter.go index 3559f8a..25a9b23 100644 --- a/pkg/interpreter/interpreter.go +++ b/pkg/interpreter/interpreter.go @@ -2,14 +2,15 @@ package interpreter import ( "fmt" + "io" "os" "path/filepath" + "strconv" "strings" "github.com/burnlang/burn/pkg/ast" "github.com/burnlang/burn/pkg/lexer" "github.com/burnlang/burn/pkg/parser" - "github.com/burnlang/burn/pkg/stdlib" ) type Interpreter struct { @@ -20,6 +21,8 @@ type Interpreter struct { errorPos int importedModules map[string]bool + + stdout io.Writer } type Environment struct { @@ -40,78 +43,80 @@ func New() *Interpreter { functions: make(map[string]*ast.FunctionDeclaration), types: make(map[string]*ast.TypeDefinition), classes: make(map[string]*Class), - errorPos: 0, importedModules: make(map[string]bool), + stdout: os.Stdout, } i.addBuiltins() return i } -func (i *Interpreter) RegisterBuiltinStandardLibraries() { - - i.registerDateLibrary() - i.registerHTTPLibrary() - i.registerTimeLibrary() - - for name, lib := range stdlib.StdLibFiles { - if name == "date" || name == "http" || name == "time" { - - continue - } - _ = i.interpretStdLib(name, lib) - } -} - func (i *Interpreter) Interpret(program *ast.Program) (Value, error) { + var result Value for _, decl := range program.Declarations { if typeDef, ok := decl.(*ast.TypeDefinition); ok { i.types[typeDef.Name] = typeDef } else if classDef, ok := decl.(*ast.ClassDeclaration); ok { - class := NewClass(classDef.Name) + + class := &Class{ + Name: classDef.Name, + Methods: make(map[string]*ast.FunctionDeclaration), + Statics: make(map[string]*ast.FunctionDeclaration), + } + for _, method := range classDef.Methods { - class.AddMethod(method.Name, method) + class.Methods[method.Name] = method + class.Statics[method.Name] = method } + for _, method := range classDef.StaticMethods { - class.AddStatic(method.Name, method) + class.Statics[method.Name] = method } + i.classes[classDef.Name] = class + i.environment[classDef.Name] = class } } - i.addBuiltins() - - i.RegisterBuiltinStandardLibraries() - for _, decl := range program.Declarations { - if fn, ok := decl.(*ast.FunctionDeclaration); ok { - i.functions[fn.Name] = fn - } if imp, ok := decl.(*ast.ImportDeclaration); ok { if err := i.handleImport(imp); err != nil { return nil, err } + continue } + if multiImp, ok := decl.(*ast.MultiImportDeclaration); ok { for _, imp := range multiImp.Imports { if err := i.handleImport(imp); err != nil { return nil, err } } + continue } - } - if mainFn, exists := i.functions["main"]; exists { - return i.executeFunction(mainFn, []Value{}) - } + if _, ok := decl.(*ast.ClassDeclaration); ok { + continue + } + if _, ok := decl.(*ast.TypeDefinition); ok { + continue + } - var result Value - for _, decl := range program.Declarations { - var err error - result, err = i.executeDeclaration(decl) + val, err := i.executeDeclaration(decl) if err != nil { return nil, err } + result = val + } + + if mainFunc, exists := i.functions["main"]; exists { + mainResult, err := i.executeFunction(mainFunc, []Value{}) + if err != nil { + return nil, fmt.Errorf("error in main function: %v", err) + } + if mainResult != nil { + result = mainResult + } } return result, nil @@ -132,162 +137,85 @@ func (i *Interpreter) handleImport(imp *ast.ImportDeclaration) error { switch basename { case "date": - i.registerDateLibrary() + return nil case "http": - i.registerHTTPLibrary() + return nil case "time": - i.registerTimeLibrary() + return nil + default: + return fmt.Errorf("unknown standard library: %s", basename) } } if strings.HasSuffix(libName, ".bn") || !strings.Contains(libName, ".") { - path := libName - - if !strings.HasSuffix(path, ".bn") { - path = path + ".bn" - } - - workingDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("error getting current directory: %v", err) - } - - searchPaths := []string{ - path, - filepath.Join(workingDir, path), - filepath.Join("src", "lib", "std", path), - filepath.Join("src", "lib", path), - filepath.Join("src", "lib", "std", strings.TrimSuffix(path, ".bn")+".bn"), - filepath.Join("src", "lib", strings.TrimSuffix(path, ".bn")+".bn"), - - filepath.Join("test", strings.TrimPrefix(path, "test/")), - } - - var source []byte - var foundPath string - - for _, searchPath := range searchPaths { - source, err = os.ReadFile(searchPath) - if err == nil { - foundPath = searchPath - break - } - } - - if foundPath == "" { - baseName := filepath.Base(strings.TrimSuffix(libName, ".bn")) - if lib, exists := stdlib.StdLibFiles[baseName]; exists { - switch baseName { - case "date": - i.registerDateLibrary() - return nil - case "http": - i.registerHTTPLibrary() - return nil - case "time": - i.registerTimeLibrary() - return nil - default: - return i.interpretStdLib(baseName, lib) - } - } - - return fmt.Errorf("could not find import file: %s (tried paths: %v)", libName, searchPaths) - } - - l := lexer.New(string(source)) - tokens, err := l.Tokenize() - if err != nil { - return fmt.Errorf("lexical error in import %s: %v", foundPath, err) - } - - p := parser.New(tokens) - program, err := p.Parse() - if err != nil { - return fmt.Errorf("parse error in import %s: %v", foundPath, err) - } - - importInterpreter := New() - importInterpreter.addBuiltins() - importInterpreter.RegisterBuiltinStandardLibraries() - - for mod := range i.importedModules { - importInterpreter.importedModules[mod] = true - } - - _, err = importInterpreter.Interpret(program) - if err != nil { - return fmt.Errorf("error interpreting import %s: %v", foundPath, err) - } - - for name, typeDef := range importInterpreter.types { - i.types[name] = typeDef - } - - for name, fn := range importInterpreter.functions { - if name != "main" { - i.functions[name] = fn - } - } + return i.handleFileImport(libName) + } - for name, class := range importInterpreter.classes { - i.classes[name] = class - } + return fmt.Errorf("could not find import: %s", imp.Path) +} - for name, value := range importInterpreter.environment { - if _, exists := i.environment[name]; !exists { - i.environment[name] = value - } - } +func (i *Interpreter) handleFileImport(libName string) error { + path := libName + if !strings.HasSuffix(path, ".bn") { + path = path + ".bn" + } - return nil + workingDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current directory: %v", err) } - basename := filepath.Base(libName) - if strings.HasSuffix(basename, ".bn") { - basename = strings.TrimSuffix(basename, ".bn") + searchPaths := []string{ + path, + filepath.Join(workingDir, path), + filepath.Join("test", path), + filepath.Join("src", path), + filepath.Join(".", path), } - if lib, exists := stdlib.StdLibFiles[basename]; exists { - switch basename { - case "date": - i.registerDateLibrary() - case "http": - i.registerHTTPLibrary() - case "time": - i.registerTimeLibrary() - default: - return i.interpretStdLib(basename, lib) + var source []byte + var foundPath string + + for _, searchPath := range searchPaths { + source, err = os.ReadFile(searchPath) + if err == nil { + foundPath = searchPath + break } - return nil } - return fmt.Errorf("could not find import: %s", imp.Path) -} + if foundPath == "" { + return fmt.Errorf("could not find import file: %s (tried paths: %v)", libName, searchPaths) + } -func (i *Interpreter) interpretStdLib(name, source string) error { - l := lexer.New(source) + l := lexer.New(string(source)) tokens, err := l.Tokenize() if err != nil { - return err + return fmt.Errorf("lexical error in import %s: %v", foundPath, err) } p := parser.New(tokens) program, err := p.Parse() if err != nil { - return err + return fmt.Errorf("parse error in import %s: %v", foundPath, err) } importInterpreter := New() - importInterpreter.addBuiltins() - importInterpreter.RegisterBuiltinStandardLibraries() + + for mod := range i.importedModules { + importInterpreter.importedModules[mod] = true + } _, err = importInterpreter.Interpret(program) if err != nil { - return err + return fmt.Errorf("error interpreting import %s: %v", foundPath, err) + } + + for name, typeDef := range importInterpreter.types { + i.types[name] = typeDef } for name, fn := range importInterpreter.functions { @@ -310,123 +238,69 @@ func (i *Interpreter) interpretStdLib(name, source string) error { } func (i *Interpreter) executeDeclaration(decl ast.Declaration) (Value, error) { - if decl != nil { - i.setErrorPos(decl.Pos()) - } - switch d := decl.(type) { - case *ast.ClassDeclaration: - return nil, nil - case *ast.TypeDefinition: - return nil, nil case *ast.FunctionDeclaration: i.functions[d.Name] = d return nil, nil + case *ast.VariableDeclaration: + var value Value if d.Value != nil { - value, err := i.evaluateExpression(d.Value) + val, err := i.evaluateExpression(d.Value) if err != nil { return nil, err } - i.environment[d.Name] = value + value = val } + i.environment[d.Name] = value + return value, nil + + case *ast.ClassDeclaration: + + class := &Class{ + Name: d.Name, + Methods: make(map[string]*ast.FunctionDeclaration), + Statics: make(map[string]*ast.FunctionDeclaration), + } + + for _, method := range d.Methods { + class.Methods[method.Name] = method + class.Statics[method.Name] = method + } + + for _, method := range d.StaticMethods { + class.Statics[method.Name] = method + } + + i.classes[d.Name] = class + i.environment[d.Name] = class + return class, nil + + case *ast.TypeDefinition: + i.types[d.Name] = d return nil, nil + case *ast.ExpressionStatement: return i.evaluateExpression(d.Expression) + case *ast.ReturnStatement: - if d.Value == nil { - return nil, nil + if d.Value != nil { + return i.evaluateExpression(d.Value) } - return i.evaluateExpression(d.Value) + return nil, nil + case *ast.IfStatement: - condition, err := i.evaluateExpression(d.Condition) - if err != nil { - return nil, err - } + return i.executeIfStatement(d) - if cond, ok := condition.(bool); ok { - if cond { - for _, stmt := range d.ThenBranch { - result, err := i.executeDeclaration(stmt) - if err != nil { - return nil, err - } - if _, ok := stmt.(*ast.ReturnStatement); ok { - return result, nil - } - } - } else if d.ElseBranch != nil { - for _, stmt := range d.ElseBranch { - result, err := i.executeDeclaration(stmt) - if err != nil { - return nil, err - } - if _, ok := stmt.(*ast.ReturnStatement); ok { - return result, nil - } - } - } - } - return nil, nil case *ast.WhileStatement: - for { - condition, err := i.evaluateExpression(d.Condition) - if err != nil { - return nil, err - } + return i.executeWhileStatement(d) - if cond, ok := condition.(bool); ok && cond { - for _, stmt := range d.Body { - result, err := i.executeDeclaration(stmt) - if err != nil { - return nil, err - } - if _, ok := stmt.(*ast.ReturnStatement); ok { - return result, nil - } - } - } else { - break - } - } - return nil, nil case *ast.ForStatement: - if d.Initializer != nil { - _, err := i.executeDeclaration(d.Initializer) - if err != nil { - return nil, err - } - } + return i.executeForStatement(d) - for { - if d.Condition != nil { - condition, err := i.evaluateExpression(d.Condition) - if err != nil { - return nil, err - } - if cond, ok := condition.(bool); !ok || !cond { - break - } - } + case *ast.BlockStatement: + return i.executeBlockStatement(d) - for _, stmt := range d.Body { - result, err := i.executeDeclaration(stmt) - if err != nil { - return nil, err - } - if _, ok := stmt.(*ast.ReturnStatement); ok { - return result, nil - } - } - - if d.Increment != nil { - _, err := i.evaluateExpression(d.Increment) - if err != nil { - return nil, err - } - } - } - return nil, nil default: return nil, fmt.Errorf("unknown declaration type: %T", decl) } @@ -513,3 +387,172 @@ func (i *Interpreter) AddVariable(name string, value interface{}) { i.environment[name] = value } } + +func (i *Interpreter) callBuiltinFunction(name string, args []interface{}) (interface{}, error) { + switch name { + case "print": + + if len(args) == 0 { + fmt.Fprintln(i.stdout) + return nil, nil + } + + var output strings.Builder + for j, arg := range args { + if j > 0 { + output.WriteString(" ") + } + + switch v := arg.(type) { + case string: + output.WriteString(v) + case int: + output.WriteString(strconv.Itoa(v)) + case int64: + output.WriteString(strconv.FormatInt(v, 10)) + case float64: + output.WriteString(strconv.FormatFloat(v, 'f', -1, 64)) + case bool: + output.WriteString(strconv.FormatBool(v)) + case nil: + output.WriteString("null") + default: + if stringer, ok := v.(interface{ String() string }); ok { + output.WriteString(stringer.String()) + } else { + output.WriteString(fmt.Sprintf("%v", v)) + } + } + } + + fmt.Fprintln(i.stdout, output.String()) + + return nil, nil + + } + + return nil, fmt.Errorf("unknown builtin function: %s", name) +} + +func (i *Interpreter) executeIfStatement(stmt *ast.IfStatement) (Value, error) { + condition, err := i.evaluateExpression(stmt.Condition) + if err != nil { + return nil, err + } + + if i.isTruthy(condition) { + return i.executeStatements(stmt.ThenBranch) + } else if len(stmt.ElseBranch) > 0 { + return i.executeStatements(stmt.ElseBranch) + } + return nil, nil +} + +func (i *Interpreter) executeWhileStatement(stmt *ast.WhileStatement) (Value, error) { + var result Value + for { + condition, err := i.evaluateExpression(stmt.Condition) + if err != nil { + return nil, err + } + + if !i.isTruthy(condition) { + break + } + + result, err = i.executeStatements(stmt.Body) + if err != nil { + return nil, err + } + } + return result, nil +} + +func (i *Interpreter) executeForStatement(stmt *ast.ForStatement) (Value, error) { + prevEnv := make(map[string]Value) + for k, v := range i.environment { + prevEnv[k] = v + } + + var result Value + var err error + + if stmt.Initializer != nil { + _, err = i.executeDeclaration(stmt.Initializer) + if err != nil { + i.environment = prevEnv + return nil, err + } + } + + for { + if stmt.Condition != nil { + condition, err := i.evaluateExpression(stmt.Condition) + if err != nil { + i.environment = prevEnv + return nil, err + } + if !i.isTruthy(condition) { + break + } + } + + result, err = i.executeStatements(stmt.Body) + if err != nil { + i.environment = prevEnv + return nil, err + } + + if stmt.Increment != nil { + _, err = i.evaluateExpression(stmt.Increment) + if err != nil { + i.environment = prevEnv + return nil, err + } + } + } + + for k, v := range prevEnv { + if _, exists := i.environment[k]; !exists || i.environment[k] != v { + i.environment[k] = v + } + } + + return result, nil +} + +func (i *Interpreter) executeBlockStatement(stmt *ast.BlockStatement) (Value, error) { + return i.executeStatements(stmt.Statements) +} + +func (i *Interpreter) executeStatements(statements []ast.Declaration) (Value, error) { + var result Value + for _, stmt := range statements { + val, err := i.executeDeclaration(stmt) + if err != nil { + return nil, err + } + result = val + } + return result, nil +} + +func (i *Interpreter) isTruthy(value Value) bool { + if value == nil { + return false + } + if b, ok := value.(bool); ok { + return b + } + if f, ok := value.(float64); ok { + return f != 0 + } + if s, ok := value.(string); ok { + return s != "" + } + return true +} + +func (i *Interpreter) SetStdout(w io.Writer) { + i.stdout = w +} diff --git a/pkg/interpreter/stdlib_date.go b/pkg/interpreter/stdlib_date.go deleted file mode 100644 index 8d26318..0000000 --- a/pkg/interpreter/stdlib_date.go +++ /dev/null @@ -1,387 +0,0 @@ -package interpreter - -import ( - "fmt" - "time" - - "github.com/burnlang/burn/pkg/ast" -) - -func (i *Interpreter) registerDateLibrary() { - i.types["Date"] = &ast.TypeDefinition{ - Name: "Date", - Fields: []ast.TypeField{ - {Name: "year", Type: "int"}, - {Name: "month", Type: "int"}, - {Name: "day", Type: "int"}, - }, - } - - dateClass := NewClass("Date") - - - dateClass.AddStatic("now", &ast.FunctionDeclaration{ - Name: "now", - Parameters: []ast.Parameter{}, - ReturnType: "Date", - }) - - dateClass.AddStatic("today", &ast.FunctionDeclaration{ - Name: "today", - Parameters: []ast.Parameter{}, - ReturnType: "string", - }) - - dateClass.AddStatic("formatDate", &ast.FunctionDeclaration{ - Name: "formatDate", - Parameters: []ast.Parameter{ - {Name: "date", Type: "Date"}, - }, - ReturnType: "string", - }) - - dateClass.AddStatic("createDate", &ast.FunctionDeclaration{ - Name: "createDate", - Parameters: []ast.Parameter{ - {Name: "year", Type: "int"}, - {Name: "month", Type: "int"}, - {Name: "day", Type: "int"}, - }, - ReturnType: "Date", - }) - - dateClass.AddStatic("currentYear", &ast.FunctionDeclaration{ - Name: "currentYear", - Parameters: []ast.Parameter{}, - ReturnType: "int", - }) - - dateClass.AddStatic("currentMonth", &ast.FunctionDeclaration{ - Name: "currentMonth", - Parameters: []ast.Parameter{}, - ReturnType: "int", - }) - - dateClass.AddStatic("currentDay", &ast.FunctionDeclaration{ - Name: "currentDay", - Parameters: []ast.Parameter{}, - ReturnType: "int", - }) - - dateClass.AddStatic("isLeapYear", &ast.FunctionDeclaration{ - Name: "isLeapYear", - Parameters: []ast.Parameter{ - {Name: "year", Type: "int"}, - }, - ReturnType: "bool", - }) - - dateClass.AddStatic("daysInMonth", &ast.FunctionDeclaration{ - Name: "daysInMonth", - Parameters: []ast.Parameter{ - {Name: "year", Type: "int"}, - {Name: "month", Type: "int"}, - }, - ReturnType: "int", - }) - - dateClass.AddStatic("dayOfWeek", &ast.FunctionDeclaration{ - Name: "dayOfWeek", - Parameters: []ast.Parameter{ - {Name: "date", Type: "Date"}, - }, - ReturnType: "int", - }) - - dateClass.AddStatic("addDays", &ast.FunctionDeclaration{ - Name: "addDays", - Parameters: []ast.Parameter{ - {Name: "date", Type: "Date"}, - {Name: "days", Type: "int"}, - }, - ReturnType: "Date", - }) - - dateClass.AddStatic("subtractDays", &ast.FunctionDeclaration{ - Name: "subtractDays", - Parameters: []ast.Parameter{ - {Name: "date", Type: "Date"}, - {Name: "days", Type: "int"}, - }, - ReturnType: "Date", - }) - - - i.classes["Date"] = dateClass - i.environment["Date"] = dateClass - - - - i.environment["Date.now"] = &BuiltinFunction{ - Name: "Date.now", - Fn: func(args []Value) (Value, error) { - currentTime := time.Now() - return &Struct{ - TypeName: "Date", - Fields: map[string]interface{}{ - "year": currentTime.Year(), - "month": int(currentTime.Month()), - "day": currentTime.Day(), - }, - }, nil - }, - } - - i.environment["Date.today"] = &BuiltinFunction{ - Name: "Date.today", - Fn: func(args []Value) (Value, error) { - currentTime := time.Now() - year := currentTime.Year() - month := int(currentTime.Month()) - day := currentTime.Day() - monthStr := fmt.Sprintf("%02d", month) - dayStr := fmt.Sprintf("%02d", day) - return fmt.Sprintf("%d-%s-%s", year, monthStr, dayStr), nil - }, - } - - i.environment["Date.formatDate"] = &BuiltinFunction{ - Name: "Date.formatDate", - Fn: func(args []Value) (Value, error) { - if len(args) != 1 { - return nil, fmt.Errorf("Date.formatDate expects exactly one Date argument") - } - dateStruct, ok := args[0].(*Struct) - if !ok || dateStruct.TypeName != "Date" { - return nil, fmt.Errorf("Date.formatDate expects a Date struct") - } - year, _ := dateStruct.Fields["year"].(int) - month, _ := dateStruct.Fields["month"].(int) - day, _ := dateStruct.Fields["day"].(int) - monthStr := fmt.Sprintf("%02d", month) - dayStr := fmt.Sprintf("%02d", day) - return fmt.Sprintf("%d-%s-%s", year, monthStr, dayStr), nil - }, - } - - i.environment["Date.currentYear"] = &BuiltinFunction{ - Name: "Date.currentYear", - Fn: func(args []Value) (Value, error) { - return float64(time.Now().Year()), nil - }, - } - - i.environment["Date.currentMonth"] = &BuiltinFunction{ - Name: "Date.currentMonth", - Fn: func(args []Value) (Value, error) { - return float64(int(time.Now().Month())), nil - }, - } - - i.environment["Date.currentDay"] = &BuiltinFunction{ - Name: "Date.currentDay", - Fn: func(args []Value) (Value, error) { - return float64(time.Now().Day()), nil - }, - } - - i.environment["Date.isLeapYear"] = &BuiltinFunction{ - Name: "Date.isLeapYear", - Fn: func(args []Value) (Value, error) { - if len(args) != 1 { - return nil, fmt.Errorf("Date.isLeapYear expects exactly one integer argument") - } - yearFloat, ok := args[0].(float64) - if !ok { - return nil, fmt.Errorf("Date.isLeapYear expects an integer") - } - year := int(yearFloat) - isLeap := false - if year%400 == 0 { - isLeap = true - } else if year%100 == 0 { - isLeap = false - } else if year%4 == 0 { - isLeap = true - } - return isLeap, nil - }, - } - - i.environment["Date.daysInMonth"] = &BuiltinFunction{ - Name: "Date.daysInMonth", - Fn: func(args []Value) (Value, error) { - if len(args) != 2 { - return nil, fmt.Errorf("Date.daysInMonth expects exactly two integer arguments") - } - yearFloat, ok := args[0].(float64) - if !ok { - return nil, fmt.Errorf("Date.daysInMonth expects year as an integer") - } - monthFloat, ok := args[1].(float64) - if !ok { - return nil, fmt.Errorf("Date.daysInMonth expects month as an integer") - } - year := int(yearFloat) - month := int(monthFloat) - daysInMonth := 31 - if month == 4 || month == 6 || month == 9 || month == 11 { - daysInMonth = 30 - } else if month == 2 { - isLeap := false - if year%400 == 0 { - isLeap = true - } else if year%100 == 0 { - isLeap = false - } else if year%4 == 0 { - isLeap = true - } - if isLeap { - daysInMonth = 29 - } else { - daysInMonth = 28 - } - } - return float64(daysInMonth), nil - }, - } - - i.environment["Date.createDate"] = &BuiltinFunction{ - Name: "Date.createDate", - Fn: func(args []Value) (Value, error) { - if len(args) != 3 { - return nil, fmt.Errorf("Date.createDate expects exactly three integer arguments") - } - yearFloat, ok := args[0].(float64) - if !ok { - return nil, fmt.Errorf("Date.createDate expects year as an integer") - } - monthFloat, ok := args[1].(float64) - if !ok { - return nil, fmt.Errorf("Date.createDate expects month as an integer") - } - dayFloat, ok := args[2].(float64) - if !ok { - return nil, fmt.Errorf("Date.createDate expects day as an integer") - } - dateStruct := &Struct{ - TypeName: "Date", - Fields: map[string]interface{}{ - "year": int(yearFloat), - "month": int(monthFloat), - "day": int(dayFloat), - }, - } - return dateStruct, nil - }, - } - - i.environment["Date.dayOfWeek"] = &BuiltinFunction{ - Name: "Date.dayOfWeek", - Fn: func(args []Value) (Value, error) { - if len(args) != 1 { - return nil, fmt.Errorf("Date.dayOfWeek expects exactly one Date argument") - } - dateStruct, ok := args[0].(*Struct) - if !ok || dateStruct.TypeName != "Date" { - return nil, fmt.Errorf("Date.dayOfWeek expects a Date struct") - } - year, _ := dateStruct.Fields["year"].(int) - month, _ := dateStruct.Fields["month"].(int) - day, _ := dateStruct.Fields["day"].(int) - if month < 3 { - month += 12 - year-- - } - k := year % 100 - j := year / 100 - h := (day + ((13 * (month + 1)) / 5) + k + (k / 4) + (j / 4) - (2 * j)) % 7 - if h < 0 { - h += 7 - } - return float64(h), nil - }, - } - - i.environment["Date.addDays"] = &BuiltinFunction{ - Name: "Date.addDays", - Fn: func(args []Value) (Value, error) { - if len(args) != 2 { - return nil, fmt.Errorf("Date.addDays expects exactly two arguments: a Date and an integer") - } - dateStruct, ok := args[0].(*Struct) - if !ok || dateStruct.TypeName != "Date" { - return nil, fmt.Errorf("Date.addDays expects a Date struct as first argument") - } - daysFloat, ok := args[1].(float64) - if !ok { - return nil, fmt.Errorf("Date.addDays expects an integer as second argument") - } - year, _ := dateStruct.Fields["year"].(int) - month, _ := dateStruct.Fields["month"].(int) - day, _ := dateStruct.Fields["day"].(int) - t := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) - newTime := t.AddDate(0, 0, int(daysFloat)) - newDateStruct := &Struct{ - TypeName: "Date", - Fields: map[string]interface{}{ - "year": newTime.Year(), - "month": int(newTime.Month()), - "day": newTime.Day(), - }, - } - return newDateStruct, nil - }, - } - - i.environment["Date.subtractDays"] = &BuiltinFunction{ - Name: "Date.subtractDays", - Fn: func(args []Value) (Value, error) { - if len(args) != 2 { - return nil, fmt.Errorf("Date.subtractDays expects exactly two arguments: a Date and an integer") - } - dateStruct, ok := args[0].(*Struct) - if !ok || dateStruct.TypeName != "Date" { - return nil, fmt.Errorf("Date.subtractDays expects a Date struct as first argument") - } - daysFloat, ok := args[1].(float64) - if !ok { - return nil, fmt.Errorf("Date.subtractDays expects an integer as second argument") - } - year, _ := dateStruct.Fields["year"].(int) - month, _ := dateStruct.Fields["month"].(int) - day, _ := dateStruct.Fields["day"].(int) - t := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) - newTime := t.AddDate(0, 0, -int(daysFloat)) - newDateStruct := &Struct{ - TypeName: "Date", - Fields: map[string]interface{}{ - "year": newTime.Year(), - "month": int(newTime.Month()), - "day": newTime.Day(), - }, - } - return newDateStruct, nil - }, - } - - - aliases := map[string]string{ - "now": "Date.now", - "formatDate": "Date.formatDate", - "currentYear": "Date.currentYear", - "currentMonth": "Date.currentMonth", - "currentDay": "Date.currentDay", - "isLeapYear": "Date.isLeapYear", - "daysInMonth": "Date.daysInMonth", - "createDate": "Date.createDate", - "dayOfWeek": "Date.dayOfWeek", - "addDays": "Date.addDays", - "subtractDays": "Date.subtractDays", - "today": "Date.today", - } - - for oldName, newName := range aliases { - i.environment[oldName] = i.environment[newName] - } -} diff --git a/pkg/interpreter/stdlib_time.go b/pkg/interpreter/time.go similarity index 98% rename from pkg/interpreter/stdlib_time.go rename to pkg/interpreter/time.go index 6d481db..6d24a16 100644 --- a/pkg/interpreter/stdlib_time.go +++ b/pkg/interpreter/time.go @@ -7,7 +7,6 @@ import ( "github.com/burnlang/burn/pkg/ast" ) - func (i *Interpreter) registerTimeLibrary() { timeClass := NewClass("Time") @@ -46,6 +45,7 @@ func (i *Interpreter) registerTimeLibrary() { Name: "Time.now", Fn: func(args []Value) (Value, error) { return time.Now(), nil + }, } i.environment["Time.sleep"] = &BuiltinFunction{ @@ -88,7 +88,7 @@ func (i *Interpreter) registerTimeLibrary() { }, } - + // Create aliases for direct access i.environment["now"] = i.environment["Time.now"] i.environment["sleep"] = i.environment["Time.sleep"] i.environment["timestamp"] = i.environment["Time.timestamp"] diff --git a/pkg/lexer/token.go b/pkg/lexer/token.go index 3fac2aa..d1251d5 100644 --- a/pkg/lexer/token.go +++ b/pkg/lexer/token.go @@ -50,6 +50,7 @@ const ( TokenModulo TokenClass TokenTypeVoid + TokenAs ) type Token struct { @@ -80,5 +81,6 @@ func GetKeywords() map[string]TokenType { "import": TokenImport, "class": TokenClass, "void": TokenTypeVoid, + "as": TokenAs, } } diff --git a/pkg/parser/declaration.go b/pkg/parser/declaration.go index 78048ef..2a6aa2b 100644 --- a/pkg/parser/declaration.go +++ b/pkg/parser/declaration.go @@ -43,8 +43,17 @@ func (p *Parser) importDeclaration() (ast.Declaration, error) { path := p.previous().Value processedPath := p.processImportPath(path) + alias := "" + if p.match(lexer.TokenAs) { + if !p.check(lexer.TokenIdentifier) { + return nil, fmt.Errorf("expected alias name after 'as' at line %d", p.peek().Line) + } + alias = p.advance().Value + } + imports = append(imports, &ast.ImportDeclaration{ - Path: processedPath, + Path: processedPath, + Alias: alias, }) } @@ -64,16 +73,31 @@ func (p *Parser) importDeclaration() (ast.Declaration, error) { path := p.previous().Value processedPath := p.processImportPath(path) + alias := "" + if p.match(lexer.TokenAs) { + if !p.check(lexer.TokenIdentifier) { + return nil, fmt.Errorf("expected alias name after 'as' at line %d", p.peek().Line) + } + alias = p.advance().Value + } + return &ast.ImportDeclaration{ - Path: processedPath, + Path: processedPath, + Alias: alias, }, nil } func (p *Parser) processImportPath(path string) string { trimmedPath := strings.Trim(path, "\"") + if strings.HasPrefix(trimmedPath, "std/") || + (!strings.Contains(trimmedPath, "/") && !strings.Contains(trimmedPath, "\\") && + (trimmedPath == "date" || trimmedPath == "http" || trimmedPath == "time")) { + return trimmedPath + } + if !strings.Contains(trimmedPath, "/") && !strings.Contains(trimmedPath, "\\") { - return "src/lib/std/" + trimmedPath + ".bn" + return trimmedPath + ".bn" } if strings.HasSuffix(trimmedPath, ".bn") { diff --git a/pkg/stdlib/datelib.go b/pkg/stdlib/datelib.go deleted file mode 100644 index b3b2ff1..0000000 --- a/pkg/stdlib/datelib.go +++ /dev/null @@ -1,75 +0,0 @@ -package stdlib - -// DateLib contains the source code for the date standard library -const DateLib = `// Burn Standard Library: Date Module -// This provides date and time functionality for Burn programs - -// Date struct to represent a date with day, month, and year -type Date { - year: int, - month: int, - day: int -} - -// Date class with static methods -class Date { - // Get the current date as a Date struct - static fun now(): Date { - // Implementation provided by interpreter - } - - // Format a date as string (YYYY-MM-DD) - static fun formatDate(date: Date): string { - // Implementation provided by interpreter - } - - // Get current year - static fun currentYear(): int { - // Implementation provided by interpreter - } - - // Get current month - static fun currentMonth(): int { - // Implementation provided by interpreter - } - - // Get current day - static fun currentDay(): int { - // Implementation provided by interpreter - } - - // Check if a year is a leap year - static fun isLeapYear(year: int): bool { - // Implementation provided by interpreter - } - - // Get the number of days in a specific month of a specific year - static fun daysInMonth(year: int, month: int): int { - // Implementation provided by interpreter - } - - // Create a Date from year, month, and day - static fun createDate(year: int, month: int, day: int): Date { - // Implementation provided by interpreter - } - - // Get the day of the week (0 = Saturday, 1 = Sunday, ..., 6 = Friday) - static fun dayOfWeek(date: Date): int { - // Implementation provided by interpreter - } - - // Add days to a date - static fun addDays(date: Date, days: int): Date { - // Implementation provided by interpreter - } - - // Subtract days from a date - static fun subtractDays(date: Date, days: int): Date { - // Implementation provided by interpreter - } - - // Get today's date as a formatted string - static fun today(): string { - // Implementation provided by interpreter - } -}` diff --git a/pkg/stdlib/httplib.go b/pkg/stdlib/httplib.go deleted file mode 100644 index 71da948..0000000 --- a/pkg/stdlib/httplib.go +++ /dev/null @@ -1,51 +0,0 @@ -package stdlib - -// HTTPLib contains the source code for the HTTP standard library -const HTTPLib = `// Burn Standard Library: HTTP Module -// This provides HTTP request functionality for Burn programs - -// HTTPResponse struct to represent an HTTP response -type HTTPResponse { - statusCode: int, - body: string, - headers: [string] -} - -// HTTP class with static methods for making HTTP requests -class HTTP { - // Make a GET request to the specified URL - static fun get(url: string): HTTPResponse { - // Implementation provided by interpreter - } - - // Make a POST request to the specified URL with the given body - static fun post(url: string, body: string): HTTPResponse { - // Implementation provided by interpreter - } - - // Make a PUT request to the specified URL with the given body - static fun put(url: string, body: string): HTTPResponse { - // Implementation provided by interpreter - } - - // Make a DELETE request to the specified URL - static fun delete(url: string): HTTPResponse { - // Implementation provided by interpreter - } - - // Set HTTP headers for subsequent requests - static fun setHeaders(headers: [string]): bool { - // Implementation provided by interpreter - } - - // Get a specific header from an HTTP response - static fun getHeader(response: HTTPResponse, name: string): string { - // Implementation provided by interpreter - } - - // Parse JSON from a string into Burn data structures - static fun parseJSON(body: string): any { - // Implementation provided by interpreter - } -} -` diff --git a/pkg/stdlib/stdlib.go b/pkg/stdlib/stdlib.go deleted file mode 100644 index e4c2496..0000000 --- a/pkg/stdlib/stdlib.go +++ /dev/null @@ -1,69 +0,0 @@ -package stdlib - -import ( - "os" - "path/filepath" - "strings" -) - -type Library struct { - Name string - Content string -} - -var RegisteredLibs = []Library{ - {Name: "date", Content: DateLib}, - {Name: "http", Content: HTTPLib}, - {Name: "time", Content: TimeLib}, -} - -var StdLibFiles = initStdLibFiles() - -func initStdLibFiles() map[string]string { - result := make(map[string]string) - for _, lib := range RegisteredLibs { - result[lib.Name] = lib.Content - } - return result -} - -func RegisterLibrary(name string, content string) { - RegisteredLibs = append(RegisteredLibs, Library{ - Name: name, - Content: content, - }) - StdLibFiles[name] = content -} - -func AutoRegisterLibraryFromFile(path string) error { - content, err := os.ReadFile(path) - if err != nil { - return err - } - - name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) - RegisterLibrary(name, string(content)) - return nil -} - -func AutoRegisterLibrariesFromDir(dirPath string) error { - entries, err := os.ReadDir(dirPath) - if err != nil { - return err - } - - for _, entry := range entries { - if entry.IsDir() { - continue - } - - if filepath.Ext(entry.Name()) == ".bn" { - filePath := filepath.Join(dirPath, entry.Name()) - if err := AutoRegisterLibraryFromFile(filePath); err != nil { - return err - } - } - } - - return nil -} diff --git a/pkg/stdlib/timelib.go b/pkg/stdlib/timelib.go deleted file mode 100644 index 96ce455..0000000 --- a/pkg/stdlib/timelib.go +++ /dev/null @@ -1,49 +0,0 @@ -package stdlib - -// TimeLib contains the source code for the time standard library -const TimeLib = `// Burn Standard Library: Time Module -// This provides time conversion functionality for Burn programs - -// Define a Time struct to represent time values -type Time { - hours: int, - minutes: int, - seconds: int, - milliseconds: int -} - -// Create a new Time object with integer parameters -fun createTime(hours: int, minutes: int, seconds: int, milliseconds: int): Time { - return { - hours: hours, - minutes: minutes, - seconds: seconds, - milliseconds: milliseconds - } -} - -// Get the current time -fun now(): Time { - // Implementation provided by interpreter -} - -// Format a time as a string (HH:MM:SS) -fun formatTime(time: Time): string { - // Implementation provided by interpreter -} - -// Add hours to a time -fun addHours(time: Time, hours: int): Time { - // Implementation provided by interpreter -} - -// Add minutes to a time -fun addMinutes(time: Time, minutes: int): Time { - // Implementation provided by interpreter -} - -// Add seconds to a time -fun addSeconds(time: Time, seconds: int): Time { - // Implementation provided by interpreter -} -` diff --git a/pkg/typechecker/expression.go b/pkg/typechecker/expression.go index a2f7362..3e7f3c6 100644 --- a/pkg/typechecker/expression.go +++ b/pkg/typechecker/expression.go @@ -165,26 +165,7 @@ func (t *TypeChecker) checkCallExpression(expr *ast.CallExpression) (string, err className := classNameExpr.Name methodName := getExpr.Name - classMethodCall := &ast.ClassMethodCallExpression{ - ClassName: className, - MethodName: methodName, - Arguments: expr.Arguments, - IsStatic: false, - Position: expr.Position, - } - - return t.checkClassMethodCallExpression(classMethodCall) - } - } - - if getExpr, ok := expr.Callee.(*ast.GetExpression); ok { - - if classExpr, ok := getExpr.Object.(*ast.VariableExpression); ok { - className := classExpr.Name - methodName := getExpr.Name - if _, exists := t.classes[className]; exists { - classMethodCall := &ast.ClassMethodCallExpression{ ClassName: className, MethodName: methodName, @@ -195,6 +176,16 @@ func (t *TypeChecker) checkCallExpression(expr *ast.CallExpression) (string, err return t.checkClassMethodCallExpression(classMethodCall) } + + classMethodCall := &ast.ClassMethodCallExpression{ + ClassName: className, + MethodName: methodName, + Arguments: expr.Arguments, + IsStatic: false, + Position: expr.Position, + } + + return t.checkClassMethodCallExpression(classMethodCall) } } @@ -208,6 +199,22 @@ func (t *TypeChecker) checkCallExpression(expr *ast.CallExpression) (string, err return "", fmt.Errorf("undefined function: %s", callee.Name) } + if callee.Name == "input" { + if len(expr.Arguments) > 1 { + return "", fmt.Errorf("function input expects 0 or 1 arguments but got %d", len(expr.Arguments)) + } + if len(expr.Arguments) == 1 { + argType, err := t.checkExpression(expr.Arguments[0]) + if err != nil { + return "", err + } + if argType != "string" { + return "", fmt.Errorf("argument 1 of function input expects string but got %s", argType) + } + } + return fn.ReturnType, nil + } + if len(expr.Arguments) != len(fn.Parameters) { return "", fmt.Errorf("function %s expects %d arguments but got %d", callee.Name, len(fn.Parameters), len(expr.Arguments)) @@ -261,6 +268,10 @@ func (t *TypeChecker) checkGetExpression(expr *ast.GetExpression) (string, error return "", err } + if objectType == "object" { + return "any", nil + } + typeDef, exists := t.types[objectType] if !exists { return "", fmt.Errorf("cannot access field on non-struct type: %s", objectType) diff --git a/pkg/typechecker/stdlib.go b/pkg/typechecker/stdlib.go deleted file mode 100644 index e53dbcb..0000000 --- a/pkg/typechecker/stdlib.go +++ /dev/null @@ -1,127 +0,0 @@ -package typechecker - -func initStandardLibrary(tc *TypeChecker) { - - tc.functions["print"] = FunctionType{ - Parameters: []string{"any"}, - ReturnType: "", - } - - tc.functions["toString"] = FunctionType{ - Parameters: []string{"any"}, - ReturnType: "string", - } - - tc.functions["input"] = FunctionType{ - Parameters: []string{"string"}, - ReturnType: "string", - } - - tc.functions["now"] = FunctionType{ - Parameters: []string{}, - ReturnType: "Date", - } - - tc.functions["formatDate"] = FunctionType{ - Parameters: []string{"Date"}, - ReturnType: "string", - } - - tc.functions["createDate"] = FunctionType{ - Parameters: []string{"int", "int", "int"}, - ReturnType: "Date", - } - - tc.functions["currentYear"] = FunctionType{ - Parameters: []string{}, - ReturnType: "int", - } - - tc.functions["currentMonth"] = FunctionType{ - Parameters: []string{}, - ReturnType: "int", - } - - tc.functions["currentDay"] = FunctionType{ - Parameters: []string{}, - ReturnType: "int", - } - - tc.functions["isLeapYear"] = FunctionType{ - Parameters: []string{"int"}, - ReturnType: "bool", - } - - tc.functions["daysInMonth"] = FunctionType{ - Parameters: []string{"int", "int"}, - ReturnType: "int", - } - - tc.functions["dayOfWeek"] = FunctionType{ - Parameters: []string{"Date"}, - ReturnType: "int", - } - - tc.functions["today"] = FunctionType{ - Parameters: []string{}, - ReturnType: "string", - } - - tc.functions["addDays"] = FunctionType{ - Parameters: []string{"Date", "int"}, - ReturnType: "Date", - } - - tc.functions["subtractDays"] = FunctionType{ - Parameters: []string{"Date", "int"}, - ReturnType: "Date", - } - - tc.types["Date"] = map[string]string{ - "year": "int", - "month": "int", - "day": "int", - } - - tc.types["array"] = map[string]string{} - tc.types["any"] = map[string]string{} - tc.types["void"] = map[string]string{} - tc.types["Object"] = map[string]string{} - - tc.types["HTTPResponse"] = map[string]string{ - "statusCode": "int", - "body": "string", - "headers": "array", - } - - tc.classes["HTTP"] = map[string]FunctionType{ - "get": { - Parameters: []string{"string"}, - ReturnType: "HTTPResponse", - }, - "post": { - Parameters: []string{"string", "string"}, - ReturnType: "HTTPResponse", - }, - "put": { - Parameters: []string{"string", "string"}, - ReturnType: "HTTPResponse", - }, - "delete": { - Parameters: []string{"string"}, - ReturnType: "HTTPResponse", - }, - "setHeaders": { - Parameters: []string{"array"}, - ReturnType: "bool", - }, - "getHeader": { - Parameters: []string{"HTTPResponse", "string"}, - ReturnType: "string", - }, - "parseJSON": { - Parameters: []string{"string"}, - ReturnType: "any", - }, - } -} diff --git a/pkg/typechecker/typechecker.go b/pkg/typechecker/typechecker.go index a4f9525..36d13e1 100644 --- a/pkg/typechecker/typechecker.go +++ b/pkg/typechecker/typechecker.go @@ -3,7 +3,9 @@ package typechecker import ( "fmt" "io/ioutil" + "os" "path/filepath" + "strings" "github.com/burnlang/burn/pkg/ast" "github.com/burnlang/burn/pkg/lexer" @@ -23,6 +25,7 @@ type TypeChecker struct { arrayTypes map[string]string currentFn string errorPos int + BaseDir string } func New() *TypeChecker { @@ -34,14 +37,57 @@ func New() *TypeChecker { arrayTypes: make(map[string]string), currentFn: "", errorPos: 0, + BaseDir: ".", } initStandardLibrary(tc) return tc } +func initStandardLibrary(tc *TypeChecker) { + + tc.functions["print"] = FunctionType{ + Parameters: []string{"any"}, + ReturnType: "void", + } + + tc.functions["println"] = FunctionType{ + Parameters: []string{"any"}, + ReturnType: "void", + } + + tc.functions["input"] = FunctionType{ + Parameters: []string{}, + ReturnType: "string", + } + + tc.functions["toString"] = FunctionType{ + Parameters: []string{"any"}, + ReturnType: "string", + } + + tc.functions["toInt"] = FunctionType{ + Parameters: []string{"any"}, + ReturnType: "int", + } + + tc.functions["toFloat"] = FunctionType{ + Parameters: []string{"any"}, + ReturnType: "float", + } + + tc.functions["len"] = FunctionType{ + Parameters: []string{"any"}, + ReturnType: "int", + } +} + func (t *TypeChecker) Check(program []ast.Declaration) error { + if err := t.processImports(program, t.BaseDir); err != nil { + return err + } + if err := t.registerTypes(program); err != nil { return err } @@ -165,6 +211,8 @@ func (t *TypeChecker) CheckFile(filename string) error { return err } + t.BaseDir = filepath.Dir(filename) + l := lexer.New(string(data)) tokens, err := l.Tokenize() if err != nil { @@ -177,7 +225,7 @@ func (t *TypeChecker) CheckFile(filename string) error { return err } - if err := t.processImports(program.Declarations, filepath.Dir(filename)); err != nil { + if err := t.processImports(program.Declarations, t.BaseDir); err != nil { return err } @@ -202,10 +250,69 @@ func (t *TypeChecker) processImports(program []ast.Declaration, baseDir string) } func (t *TypeChecker) processImport(imp *ast.ImportDeclaration, baseDir string) error { - importPath := filepath.Join(baseDir, imp.Path) - data, err := ioutil.ReadFile(importPath) - if err != nil { - return fmt.Errorf("could not import %s: %v", imp.Path, err) + + if strings.HasPrefix(imp.Path, "std/") || + (!strings.Contains(imp.Path, "/") && !strings.Contains(imp.Path, "\\") && + (imp.Path == "date" || imp.Path == "http" || imp.Path == "time")) { + + basename := strings.TrimPrefix(imp.Path, "std/") + basename = strings.TrimSuffix(basename, ".bn") + + className := basename + if imp.Alias != "" { + className = imp.Alias + } + + switch basename { + case "date": + t.registerDateLibrary(className) + return nil + case "http": + t.registerHTTPLibrary(className) + return nil + case "time": + t.registerTimeLibrary(className) + return nil + default: + return fmt.Errorf("standard library module '%s' not found", basename) + } + } + + path := imp.Path + if !strings.HasSuffix(path, ".bn") { + path = path + ".bn" + } + + searchPaths := []string{ + path, + filepath.Join(baseDir, path), + filepath.Join("test", path), + filepath.Join("src", path), + filepath.Join(".", path), + } + + if workingDir, err := os.Getwd(); err == nil { + searchPaths = append(searchPaths, + filepath.Join(workingDir, path), + filepath.Join(workingDir, "test", path), + filepath.Join(workingDir, "src", path), + ) + } + + var data []byte + var err error + var foundPath string + + for _, searchPath := range searchPaths { + data, err = ioutil.ReadFile(searchPath) + if err == nil { + foundPath = searchPath + break + } + } + + if foundPath == "" { + return fmt.Errorf("could not import %s: file not found in search paths %v", imp.Path, searchPaths) } l := lexer.New(string(data)) @@ -223,96 +330,352 @@ func (t *TypeChecker) processImport(imp *ast.ImportDeclaration, baseDir string) return t.registerImportedDeclarations(importProgram.Declarations, imp) } -func (t *TypeChecker) registerImportedDeclarations(declarations []ast.Declaration, imp *ast.ImportDeclaration) error { +func (t *TypeChecker) qualifyType(typeName string, aliasPrefix string, localTypes map[string]struct{}) string { + if aliasPrefix == "" { + return typeName + } - for _, decl := range declarations { - if typeDef, ok := decl.(*ast.TypeDefinition); ok { + if strings.HasPrefix(typeName, "[]") { + baseType := strings.TrimPrefix(typeName, "[]") + if _, isLocalBase := localTypes[baseType]; isLocalBase { + return "[]" + aliasPrefix + baseType + } + } - if _, exists := t.types[typeDef.Name]; exists { - continue - } + if _, isLocal := localTypes[typeName]; isLocal { + return aliasPrefix + typeName + } - fields := make(map[string]string) - for _, field := range typeDef.Fields { - fields[field.Name] = field.Type - } - t.types[typeDef.Name] = fields + return typeName +} - } else if class, ok := decl.(*ast.ClassDeclaration); ok { +func (t *TypeChecker) registerImportedDeclarations(declarations []ast.Declaration, imp *ast.ImportDeclaration) error { - if _, exists := t.classes[class.Name]; exists { - continue - } + aliasPrefix := "" + if imp.Alias != "" { + aliasPrefix = imp.Alias + "." + } - if _, exists := t.types[class.Name]; !exists { - t.types[class.Name] = make(map[string]string) - } + localTypesInImport := make(map[string]struct{}) + for _, decl := range declarations { + if td, ok := decl.(*ast.TypeDefinition); ok { + localTypesInImport[td.Name] = struct{}{} + } else if cd, ok := decl.(*ast.ClassDeclaration); ok { + localTypesInImport[cd.Name] = struct{}{} } } for _, decl := range declarations { - if fn, ok := decl.(*ast.FunctionDeclaration); ok { - - if _, exists := t.functions[fn.Name]; exists { - continue + switch d := decl.(type) { + case *ast.FunctionDeclaration: + fnName := aliasPrefix + d.Name + if _, exists := t.functions[fnName]; exists { + return fmt.Errorf("imported function %s is already defined", fnName) } - paramTypes := make([]string, len(fn.Parameters)) - for i, param := range fn.Parameters { - paramTypes[i] = param.Type + paramTypes := make([]string, len(d.Parameters)) + for i, param := range d.Parameters { + paramTypes[i] = t.qualifyType(param.Type, aliasPrefix, localTypesInImport) } + returnType := t.qualifyType(d.ReturnType, aliasPrefix, localTypesInImport) - t.functions[fn.Name] = FunctionType{ + t.functions[fnName] = FunctionType{ Parameters: paramTypes, - ReturnType: fn.ReturnType, + ReturnType: returnType, } - } else if class, ok := decl.(*ast.ClassDeclaration); ok { - if _, exists := t.classes[class.Name]; !exists { - classMethods := make(map[string]FunctionType) - t.classes[class.Name] = classMethods - - for _, method := range class.Methods { - paramTypes := make([]string, len(method.Parameters)) - for i, param := range method.Parameters { - paramTypes[i] = param.Type - } - - classMethods[method.Name] = FunctionType{ - Parameters: paramTypes, - ReturnType: method.ReturnType, - } - - t.functions[class.Name+"."+method.Name] = FunctionType{ - Parameters: paramTypes, - ReturnType: method.ReturnType, - } + case *ast.ClassDeclaration: + className := aliasPrefix + d.Name + if _, exists := t.classes[className]; exists { + return fmt.Errorf("imported class %s is already defined", className) + } + + classMethods := make(map[string]FunctionType) + t.classes[className] = classMethods + + t.types[className] = make(map[string]string) + + for _, method := range d.Methods { + methodName := method.Name + if _, exists := classMethods[methodName]; exists { + return fmt.Errorf("method %s is already defined in imported class %s", methodName, className) + } + + paramTypes := make([]string, len(method.Parameters)) + for i, param := range method.Parameters { + paramTypes[i] = t.qualifyType(param.Type, aliasPrefix, localTypesInImport) + } + returnType := t.qualifyType(method.ReturnType, aliasPrefix, localTypesInImport) + + classMethods[methodName] = FunctionType{ + Parameters: paramTypes, + ReturnType: returnType, + } + + t.functions[className+"."+methodName] = FunctionType{ + Parameters: paramTypes, + ReturnType: returnType, + } + } + + for _, method := range d.StaticMethods { + methodKey := "static." + method.Name + if _, exists := classMethods[methodKey]; exists { + return fmt.Errorf("static method %s is already defined in imported class %s", method.Name, className) } + paramTypes := make([]string, len(method.Parameters)) + for i, param := range method.Parameters { + paramTypes[i] = t.qualifyType(param.Type, aliasPrefix, localTypesInImport) + } + returnType := t.qualifyType(method.ReturnType, aliasPrefix, localTypesInImport) + + classMethods[methodKey] = FunctionType{ + Parameters: paramTypes, + ReturnType: returnType, + } + + t.functions[className+".static."+method.Name] = FunctionType{ + Parameters: paramTypes, + ReturnType: returnType, + } + + t.functions[className+"."+method.Name] = FunctionType{ + Parameters: paramTypes, + ReturnType: returnType, + } + } + + case *ast.TypeDefinition: + typeName := aliasPrefix + d.Name + if _, exists := t.types[typeName]; exists { + return fmt.Errorf("imported type %s is already defined", typeName) + } + fields := make(map[string]string) - for _, method := range class.StaticMethods { - methodKey := "static." + method.Name - paramTypes := make([]string, len(method.Parameters)) - for i, param := range method.Parameters { - paramTypes[i] = param.Type - } - - classMethods[methodKey] = FunctionType{ - Parameters: paramTypes, - ReturnType: method.ReturnType, - } - - t.functions[class.Name+".static."+method.Name] = FunctionType{ - Parameters: paramTypes, - ReturnType: method.ReturnType, - } + for _, field := range d.Fields { + if _, exists := fields[field.Name]; exists { + return fmt.Errorf("field %s is already defined in imported type %s", field.Name, typeName) } + fields[field.Name] = t.qualifyType(field.Type, aliasPrefix, localTypesInImport) } + t.types[typeName] = fields } } - return nil } +func (t *TypeChecker) registerHTTPLibrary(className string) { + + t.classes[className] = make(map[string]FunctionType) + t.types[className] = make(map[string]string) + + httpMethods := []struct { + name string + params []string + returnType string + }{ + {"get", []string{"string"}, "HTTPResponse"}, + {"post", []string{"string", "string"}, "HTTPResponse"}, + {"put", []string{"string", "string"}, "HTTPResponse"}, + {"delete", []string{"string"}, "HTTPResponse"}, + {"setHeaders", []string{"array"}, "void"}, + {"getHeader", []string{"HTTPResponse", "string"}, "string"}, + {"parseJSON", []string{"string"}, "any"}, + } + + classMethods := t.classes[className] + + for _, method := range httpMethods { + + staticKey := "static." + method.name + methodType := FunctionType{ + Parameters: method.params, + ReturnType: method.returnType, + } + + classMethods[staticKey] = methodType + t.functions[className+".static."+method.name] = methodType + + t.functions[className+"."+method.name] = methodType + + classMethods[method.name] = methodType + } + + t.types["HTTPResponse"] = map[string]string{ + "statusCode": "int", + "body": "string", + "headers": "array", + } +} + +func (t *TypeChecker) registerDateLibrary(className string) { + + t.classes[className] = make(map[string]FunctionType) + t.types[className] = make(map[string]string) + + t.types["Date"] = map[string]string{ + "year": "int", + "month": "int", + "day": "int", + } + + t.functions[className+".now"] = FunctionType{ + Parameters: []string{}, + ReturnType: "Date", + } + + t.functions[className+".formatDate"] = FunctionType{ + Parameters: []string{"Date"}, + ReturnType: "string", + } + + t.functions[className+".parse"] = FunctionType{ + Parameters: []string{"string"}, + ReturnType: "int", + } + + t.functions[className+".currentYear"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } + + t.functions[className+".currentMonth"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } + + t.functions[className+".currentDay"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } + + t.functions[className+".isLeapYear"] = FunctionType{ + Parameters: []string{"int"}, + ReturnType: "bool", + } + + t.functions[className+".daysInMonth"] = FunctionType{ + Parameters: []string{"int", "int"}, + ReturnType: "int", + } + + t.functions[className+".createDate"] = FunctionType{ + Parameters: []string{"int", "int", "int"}, + ReturnType: "Date", + } + + t.functions[className+".dayOfWeek"] = FunctionType{ + Parameters: []string{"Date"}, + ReturnType: "int", + } + + t.functions[className+".addDays"] = FunctionType{ + Parameters: []string{"Date", "int"}, + ReturnType: "Date", + } + + t.functions[className+".subtractDays"] = FunctionType{ + Parameters: []string{"Date", "int"}, + ReturnType: "Date", + } + + t.functions[className+".today"] = FunctionType{ + Parameters: []string{}, + ReturnType: "string", + } + + t.classes[className]["static.now"] = FunctionType{ + Parameters: []string{}, + ReturnType: "Date", + } + + t.classes[className]["static.formatDate"] = FunctionType{ + Parameters: []string{"Date"}, + ReturnType: "string", + } + + t.classes[className]["static.parse"] = FunctionType{ + Parameters: []string{"string"}, + ReturnType: "int", + } + + t.classes[className]["static.currentYear"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } + + t.classes[className]["static.currentMonth"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } + + t.classes[className]["static.currentDay"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } + + t.classes[className]["static.isLeapYear"] = FunctionType{ + Parameters: []string{"int"}, + ReturnType: "bool", + } + + t.classes[className]["static.daysInMonth"] = FunctionType{ + Parameters: []string{"int", "int"}, + ReturnType: "int", + } + + t.classes[className]["static.createDate"] = FunctionType{ + Parameters: []string{"int", "int", "int"}, + ReturnType: "Date", + } + + t.classes[className]["static.dayOfWeek"] = FunctionType{ + Parameters: []string{"Date"}, + ReturnType: "int", + } + + t.classes[className]["static.addDays"] = FunctionType{ + Parameters: []string{"Date", "int"}, + ReturnType: "Date", + } + + t.classes[className]["static.subtractDays"] = FunctionType{ + Parameters: []string{"Date", "int"}, + ReturnType: "Date", + } + + t.classes[className]["static.today"] = FunctionType{ + Parameters: []string{}, + ReturnType: "string", + } +} + +func (t *TypeChecker) registerTimeLibrary(className string) { + + t.classes[className] = make(map[string]FunctionType) + t.types[className] = make(map[string]string) + + t.functions[className+".sleep"] = FunctionType{ + Parameters: []string{"int"}, + ReturnType: "void", + } + + t.functions[className+".now"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } + + timeMethods := t.classes[className] + timeMethods["static.sleep"] = FunctionType{ + Parameters: []string{"int"}, + ReturnType: "void", + } + timeMethods["static.now"] = FunctionType{ + Parameters: []string{}, + ReturnType: "int", + } +} + func (t *TypeChecker) setErrorPos(pos int) { t.errorPos = pos } diff --git a/src/lib/std/date.bn b/src/lib/std/date.bn deleted file mode 100644 index 2e5bc24..0000000 --- a/src/lib/std/date.bn +++ /dev/null @@ -1,66 +0,0 @@ -// Burn Standard Library: Date Module -// This provides date and time functionality for Burn programs - -// Date class implementation -class Date { - // Get the current date - fun now(): Date { - // Implementation is provided by the interpreter - // Returns a Date struct representing the current date - } - - // Format a date as a string (YYYY-MM-DD) - fun format(date: Date): string { - // Implementation is provided by the interpreter - } - - // Create a date from year, month, day components - fun create(year: int, month: int, day: int): Date { - // Implementation is provided by the interpreter - } - - // Get the current year - fun getCurrentYear(): int { - // Implementation is provided by the interpreter - } - - // Get the current month (1-12) - fun getCurrentMonth(): int { - // Implementation is provided by the interpreter - } - - // Get the current day of month - fun getCurrentDay(): int { - // Implementation is provided by the interpreter - } - - // Check if a year is a leap year - fun isLeapYear(year: int): bool { - // Implementation is provided by the interpreter - } - - // Get the number of days in a month - fun daysInMonth(year: int, month: int): int { - // Implementation is provided by the interpreter - } - - // Get the day of week (0=Saturday, 1=Sunday, ..., 6=Friday) - fun dayOfWeek(date: Date): int { - // Implementation is provided by the interpreter - } - - // Add days to a date - fun addDays(date: Date, days: int): Date { - // Implementation is provided by the interpreter - } - - // Subtract days from a date - fun subtractDays(date: Date, days: int): Date { - // Implementation is provided by the interpreter - } - - // Get today's date as a formatted string - fun today(): string { - return Date.format(Date.now()) - } -} \ No newline at end of file diff --git a/src/lib/std/http.bn b/src/lib/std/http.bn deleted file mode 100644 index 391c329..0000000 --- a/src/lib/std/http.bn +++ /dev/null @@ -1,40 +0,0 @@ -// Burn Standard Library: HTTP Module -// This provides HTTP request functionality for Burn programs - -// HTTP class implementation -class HTTP { - // Make a GET request to the specified URL - fun get(url: string): HTTPResponse { - // Implementation is provided by the interpreter - } - - // Make a POST request to the specified URL with the given body - fun post(url: string, body: string): HTTPResponse { - // Implementation is provided by the interpreter - } - - // Make a PUT request to the specified URL with the given body - fun put(url: string, body: string): HTTPResponse { - // Implementation is provided by the interpreter - } - - // Make a DELETE request to the specified URL - fun delete(url: string): HTTPResponse { - // Implementation is provided by the interpreter - } - - // Add custom headers to a request - fun setHeaders(headers: array): bool { - // Implementation is provided by the interpreter - } - - // Get a header value from a response - fun getHeader(response: HTTPResponse, name: string): string { - // Implementation is provided by the interpreter - } - - // Parse JSON from a response body - fun parseJSON(body: string): any { - // Implementation is provided by the interpreter - } -} \ No newline at end of file diff --git a/src/lib/std/time.bn b/src/lib/std/time.bn deleted file mode 100644 index 7bc449e..0000000 --- a/src/lib/std/time.bn +++ /dev/null @@ -1,306 +0,0 @@ -// Burn Standard Library: Time Module -// This provides time conversion functionality for Burn programs - -// Define a Time struct to represent time values -type Time { - hours: int, - minutes: int, - seconds: int, - milliseconds: int -} - -// Create a new Time object with integer parameters -fun createTime(hours: int, minutes: int, seconds: int, milliseconds: int): Time { - return { - hours: hours, - minutes: minutes, - seconds: seconds, - milliseconds: milliseconds - } -} - -// Convert time to total milliseconds -fun toMilliseconds(time: Time): int { - return (time.hours * 3600000.0) + - (time.minutes * 60000.0) + - (time.seconds * 1000.0) + - (time.milliseconds * 1.0) -} - -// Create Time from total milliseconds -fun fromMilliseconds(ms: float): Time { - var msInt = ms - var hours = (msInt / 3600000.0) - var hoursInt = hours - (hours % 1) - msInt = msInt - (hoursInt * 3600000.0) - - var minutes = (msInt / 60000.0) - var minutesInt = minutes - (minutes % 1) - msInt = msInt - (minutesInt * 60000.0) - - var seconds = (msInt / 1000.0) - var secondsInt = seconds - (seconds % 1) - var milliseconds = msInt - (secondsInt * 1000.0) - - return createTime( - hoursInt as int, - minutesInt as int, - secondsInt as int, - milliseconds as int - ) -} - -// Convert hours to minutes -fun hoursToMinutes(hours: float): float { - return hours * 60.0 -} - -// Convert minutes to hours -fun minutesToHours(minutes: float): float { - return minutes / 60.0 -} - -// Convert hours to seconds -fun hoursToSeconds(hours: float): float { - return hours * 3600.0 -} - -// Convert seconds to hours -fun secondsToHours(seconds: float): float { - return seconds / 3600.0 -} - -// Convert minutes to seconds -fun minutesToSeconds(minutes: float): float { - return minutes * 60.0 -} - -// Convert seconds to minutes -fun secondsToMinutes(seconds: float): float { - return seconds / 60.0 -} - -// Convert hours to milliseconds -fun hoursToMs(hours: float): float { - return hours * 3600000.0 -} - -// Convert milliseconds to hours -fun msToHours(ms: float): float { - return ms / 3600000.0 -} - -// Convert minutes to milliseconds -fun minutesToMs(minutes: float): float { - return minutes * 60000.0 -} - -// Convert milliseconds to minutes -fun msToMinutes(ms: float): float { - return ms / 60000.0 -} - -// Convert seconds to milliseconds -fun secondsToMs(seconds: float): float { - return seconds * 1000.0 -} - -// Convert milliseconds to seconds -fun msToSeconds(ms: float): float { - return ms / 1000.0 -} - -// Add two times -fun addTime(time1: Time, time2: Time): Time { - var ms1 = toMilliseconds(time1) - var ms2 = toMilliseconds(time2) - return fromMilliseconds(ms1 + ms2) -} - -// Subtract time2 from time1 -fun subtractTime(time1: Time, time2: Time): Time { - var ms1 = toMilliseconds(time1) - var ms2 = toMilliseconds(time2) - return fromMilliseconds(ms1 - ms2) -} - -// Format seconds into HH:MM:SS string -fun formatTime(totalSeconds: float): string { - var time = fromMilliseconds(totalSeconds * 1000.0) - - // Format with leading zeros - var hoursStr = toString(time.hours) - if (time.hours < 10) { - hoursStr = "0" + hoursStr - } - - var minutesStr = toString(time.minutes) - if (time.minutes < 10) { - minutesStr = "0" + minutesStr - } - - var secondsStr = toString(time.seconds) - if (time.seconds < 10) { - secondsStr = "0" + secondsStr - } - - return hoursStr + ":" + minutesStr + ":" + secondsStr -} - -// Format Time object to string (HH:MM:SS or HH:MM:SS.mmm) -fun formatTimeObject(time: Time, includeMs: bool): string { - // Format with leading zeros - var hoursStr = toString(time.hours) - if (time.hours < 10) { - hoursStr = "0" + hoursStr - } - - var minutesStr = toString(time.minutes) - if (time.minutes < 10) { - minutesStr = "0" + minutesStr - } - - var secondsStr = toString(time.seconds) - if (time.seconds < 10) { - secondsStr = "0" + secondsStr - } - - var timeStr = hoursStr + ":" + minutesStr + ":" + secondsStr - - if (includeMs) { - var msStr = toString(time.milliseconds) - // Pad with leading zeros if needed - if (time.milliseconds < 10) { - msStr = "00" + msStr - } else if (time.milliseconds < 100) { - msStr = "0" + msStr - } - timeStr = timeStr + "." + msStr - } - - return timeStr -} - -// Parse a time string (HH:MM:SS or HH:MM:SS.mmm) into a Time object -fun parseTimeString(timeStr: string): Time { - // Initialize components - var hours = 0 - var minutes = 0 - var seconds = 0 - var milliseconds = 0 - - // Split into parts before and after decimal point (for milliseconds) - var mainPart = timeStr - var msPart = "" - - for (var i = 0; i < size(timeStr); i = i + 1) { - if (charAt(timeStr, i) == ".") { - mainPart = substring(timeStr, 0, i) - msPart = substring(timeStr, i + 1, size(timeStr)) - break - } - } - - // Parse the HH:MM:SS part - var parts = split(mainPart, ":") - - if (size(parts) >= 1) { - hours = parseInt(parts[0]) - } - - if (size(parts) >= 2) { - minutes = parseInt(parts[1]) - } - - if (size(parts) >= 3) { - seconds = parseInt(parts[2]) - } - - // Parse milliseconds if present - if (msPart != "") { - milliseconds = parseInt(msPart) - - // Adjust based on number of digits - if (size(msPart) == 1) { - milliseconds = milliseconds * 100 - } else if (size(msPart) == 2) { - milliseconds = milliseconds * 10 - } - } - - return createTime(hours, minutes, seconds, milliseconds) -} - -// Parse a time string (HH:MM:SS) into total seconds -fun parseTime(timeStr: string): float { - var time = parseTimeString(timeStr) - return (time.hours * 3600.0) + (time.minutes * 60.0) + time.seconds + (time.milliseconds / 1000.0) -} - -// Helper function to split a string by a delimiter -fun split(str: string, delimiter: string): array { - var result = [] - var current = "" - - for (var i = 0; i < size(str); i = i + 1) { - var char = charAt(str, i) - if (char == delimiter) { - result = append(result, current) - current = "" - } else { - current = current + char - } - } - - // Add the last part - if (current != "") { - result = append(result, current) - } - - return result -} - -// Helper function to parse int from string -fun parseInt(str: string): int { - var result = 0 - for (var i = 0; i < size(str); i = i + 1) { - var char = charAt(str, i) - if (char >= "0" && char <= "9") { - result = result * 10 + (char - "0") - } - } - return result -} - -// Helper function to append to an array -fun append(arr: array, item: any): array { - var newArr = [] - for (var i = 0; i < size(arr); i = i + 1) { - newArr = newArr + [arr[i]] - } - newArr = newArr + [item] - return newArr -} - -// Helper function to get character at index -fun charAt(str: string, index: int): string { - if (index < 0 || index >= size(str)) { - return "" - } - return substring(str, index, index + 1) -} - -// Helper function to get string length -fun size(str: string): int { - return length(str) -} - -// Helper function to get substring -fun substring(str: string, start: int, end: int): string { - return getPart(str, start, end) -} - -// Current time as HH:MM:SS -fun now(): string { - return currentTimeHMS() -} \ No newline at end of file diff --git a/test/class.bn b/test/class.bn index 917e311..0b47801 100644 --- a/test/class.bn +++ b/test/class.bn @@ -40,8 +40,8 @@ class Dog { //at the main function that triggers the classes fun main() { var john = Human.create("John", 30) - print(Human.greet(john)) + println(Human.greet(john)) var rex = Dog.create("Rex", "Golden Retriever") - print(Dog.bark(rex)) + println(Dog.bark(rex)) } \ No newline at end of file diff --git a/test/const.bn b/test/const.bn index d4bbb42..5a1e3fc 100644 --- a/test/const.bn +++ b/test/const.bn @@ -22,30 +22,30 @@ fun main() { const MIN_VALUE = 0 const FACTOR = 2.5 - print("MAX_VALUE: " + toString(MAX_VALUE)) - print("MIN_VALUE: " + toString(MIN_VALUE)) - print("FACTOR: " + toString(FACTOR)) + println("MAX_VALUE: " + toString(MAX_VALUE)) + println("MIN_VALUE: " + toString(MIN_VALUE)) + println("FACTOR: " + toString(FACTOR)) // Cannot reassign constants // MAX_VALUE = 200 // This would cause an error // Using constants in expressions var result = MAX_VALUE * FACTOR - print("MAX_VALUE * FACTOR = " + toString(result)) + println("MAX_VALUE * FACTOR = " + toString(result)) // Using constants from functions - print("Circle area: " + toString(testMath())) - print("Greeting: " + testStrings()) + println("Circle area: " + toString(testMath())) + println("Greeting: " + testStrings()) // Boolean constants const IS_DEBUG = true const IS_PRODUCTION = false if (IS_DEBUG) { - print("Debug mode is enabled") + println("Debug mode is enabled") } if (!IS_PRODUCTION) { - print("Not in production mode") + println("Not in production mode") } } \ No newline at end of file diff --git a/test/date.bn b/test/date.bn index f720085..8af49ae 100644 --- a/test/date.bn +++ b/test/date.bn @@ -1,58 +1,58 @@ // Date functions test -import "std/date" +import "date" as Date fun main() { // Get current date var today = Date.now() - print("Current date:") - print("------------") - print("Year: " + toString(today.year)) - print("Month: " + toString(today.month)) - print("Day: " + toString(today.day)) - print("") + println("Current date:") + println("------------") + println("Year: " + toString(today.year)) + println("Month: " + toString(today.month)) + println("Day: " + toString(today.day)) + println("") // Get current date components using individual functions - print("Current date components:") - print("------------------------") - print("Current year: " + toString(Date.currentYear())) - print("Current month: " + toString(Date.currentMonth())) - print("Current day: " + toString(Date.currentDay())) - print("") + println("Current date components:") + println("------------------------") + println("Current year: " + toString(Date.currentYear())) + println("Current month: " + toString(Date.currentMonth())) + println("Current day: " + toString(Date.currentDay())) + println("") // Format the date - print("Formatted date: " + Date.formatDate(today)) - print("") + println("Formatted date: " + Date.formatDate(today)) + println("") // Create a custom date var birthday = Date.createDate(1990, 5, 15) - print("Custom date (1990-05-15):") - print("-------------------------") - print("Year: " + toString(birthday.year)) - print("Month: " + toString(birthday.month)) - print("Day: " + toString(birthday.day)) - print("Formatted: " + Date.formatDate(birthday)) - print("") + println("Custom date (1990-05-15):") + println("-------------------------") + println("Year: " + toString(birthday.year)) + println("Month: " + toString(birthday.month)) + println("Day: " + toString(birthday.day)) + println("Formatted: " + Date.formatDate(birthday)) + println("") // Add days to a date var futureDate = Date.addDays(today, 10) - print("Date 10 days from now: " + Date.formatDate(futureDate)) + println("Date 10 days from now: " + Date.formatDate(futureDate)) // Subtract days from a date var pastDate = Date.subtractDays(today, 10) - print("Date 10 days ago: " + Date.formatDate(pastDate)) + println("Date 10 days ago: " + Date.formatDate(pastDate)) // Check if current year is a leap year - print("Is current year a leap year? " + toString(Date.isLeapYear(today.year))) + println("Is current year a leap year? " + toString(Date.isLeapYear(today.year))) // Get days in current month - print("Days in current month: " + toString(Date.daysInMonth(today.year, today.month))) + println("Days in current month: " + toString(Date.daysInMonth(today.year, today.month))) // Get day of week (0 = Saturday, 1 = Sunday, ..., 6 = Friday) var dayOfWeekNum = Date.dayOfWeek(today) // Create array with day names in the correct order (algorithm returns 0=Saturday, 1=Sunday, etc.) var dayNames = ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] - print("Today is a " + dayNames[dayOfWeekNum]) + println("Today is a " + dayNames[dayOfWeekNum]) // Use the convenience function from the date library - print("Today's date string: " + Date.today()) + println("Today's date string: " + Date.today()) } \ No newline at end of file diff --git a/test/http.bn b/test/http.bn index 45de99a..5ecd610 100644 --- a/test/http.bn +++ b/test/http.bn @@ -1,6 +1,6 @@ // HTTP API example for Burn -import "http" +import "http" as HTTP fun main() { // Default headers are already set, but we can override them if needed @@ -11,21 +11,21 @@ fun main() { HTTP.setHeaders(headers) // Make a GET request to the anime reaction API - print("Making GET request to otakugifs API...") + println("Making GET request to otakugifs API...") var response = HTTP.get("https://api.otakugifs.xyz/gif?reaction=kiss&format=gif") - // Print response information - print("Status code: " + toString(response.statusCode)) + // println response information + println("Status code: " + toString(response.statusCode)) - // Print the raw response body - print("Response body: " + response.body) + // println the raw response body + println("Response body: " + response.body) // Parse the JSON response var jsonData = HTTP.parseJSON(response.body) // Check content type header var contentType = HTTP.getHeader(response, "Content-Type") - print("Content-Type: " + contentType) + println("Content-Type: " + contentType) - print("\nAPI response successfully retrieved!") + println("\nAPI response successfully retrieved!") } \ No newline at end of file diff --git a/test/if.bn b/test/if.bn index d3b6c3d..404d861 100644 --- a/test/if.bn +++ b/test/if.bn @@ -4,20 +4,20 @@ var counter = 0 // If statements if (x > 5) { - print("x is greater than 5") + println("x is greater than 5") } else if (x == 5) { - print("x equals 5") + println("x equals 5") } else { - print("x is less than 5") + println("x is less than 5") } // While loops while (counter < 3) { - print("Counter: " + toString(counter)) + println("Counter: " + toString(counter)) counter = counter + 1 } // For loops for (var i = 0; i < 3; i = i + 1) { - print("Loop iteration: " + toString(i)) + println("Loop iteration: " + toString(i)) } diff --git a/test/import.bn b/test/import.bn index 853b45b..fa9975c 100644 --- a/test/import.bn +++ b/test/import.bn @@ -11,5 +11,5 @@ import "test/utils.bn" //main fun with using the imported functions fun main() { var num = power(2, 3) // Uses imported power function - print("2^3 = " + toString(num)) + println("2^3 = " + toString(num)) } \ No newline at end of file diff --git a/test/input.bn b/test/input.bn index a5adaf3..79e3a25 100644 --- a/test/input.bn +++ b/test/input.bn @@ -6,8 +6,8 @@ type Human { fun main() { var name = input("Enter your name: ") - print("Hello, " + name) + println("Hello, " + name) var age = input("Enter your age: ") - print("You are " + age + " years old") + println("You are " + age + " years old") } \ No newline at end of file diff --git a/test/type.bn b/test/type.bn index 0b2cb40..9742969 100644 --- a/test/type.bn +++ b/test/type.bn @@ -13,6 +13,6 @@ fun createPerson(name: string, age: int): Person { } } -//print the created person +//println the created person var person = createPerson("John", 30) -print("Person name: " + person.name) \ No newline at end of file +println("Person name: " + person.name) \ No newline at end of file