diff --git a/src/cli/main.zig b/src/cli/main.zig index 7a5ebff79d..cbaf75c4b7 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -1928,122 +1928,70 @@ pub const PlatformPaths = struct { /// Resolve platform specification from a Roc file to find both host library and platform source. /// Returns PlatformPaths with arena-allocated paths (no need to free). pub fn resolvePlatformPaths(allocs: *Allocators, roc_file_path: []const u8) (std.mem.Allocator.Error || error{ NoPlatformFound, PlatformNotSupported })!PlatformPaths { - // Read the Roc file to parse the app header - const roc_file = std.fs.cwd().openFile(roc_file_path, .{}) catch |err| switch (err) { - error.FileNotFound => return error.NoPlatformFound, - else => return error.NoPlatformFound, // Treat all file errors as no platform found - }; - defer roc_file.close(); - - const file_size = roc_file.getEndPos() catch return error.NoPlatformFound; - const source = allocs.gpa.alloc(u8, @intCast(file_size)) catch return error.OutOfMemory; - defer allocs.gpa.free(source); - _ = roc_file.read(source) catch return error.NoPlatformFound; - - // Parse the source to find the app header - // Look for "app" followed by platform specification - var lines = std.mem.splitScalar(u8, source, '\n'); - while (lines.next()) |line| { - const trimmed = std.mem.trim(u8, line, " \t\r"); - - // Check if this is an app header line - if (std.mem.startsWith(u8, trimmed, "app")) { - // Look for platform specification after "platform" - if (std.mem.indexOf(u8, trimmed, "platform")) |platform_start| { - const after_platform = trimmed[platform_start + "platform".len ..]; - - // Find the platform name/URL in quotes - if (std.mem.indexOf(u8, after_platform, "\"")) |quote_start| { - const after_quote = after_platform[quote_start + 1 ..]; - if (std.mem.indexOf(u8, after_quote, "\"")) |quote_end| { - const platform_spec = after_quote[0..quote_end]; - - // If it's a relative path, resolve it relative to the app directory - if (std.mem.startsWith(u8, platform_spec, "./") or std.mem.startsWith(u8, platform_spec, "../")) { - const app_dir = std.fs.path.dirname(roc_file_path) orelse "."; - const platform_path = try std.fs.path.join(allocs.arena, &.{ app_dir, platform_spec }); - - // Look for host library near the platform file - const platform_dir = std.fs.path.dirname(platform_path) orelse "."; - const host_filename = if (comptime builtin.target.os.tag == .windows) "host.lib" else "libhost.a"; - const host_path = try std.fs.path.join(allocs.arena, &.{ platform_dir, host_filename }); - - std.fs.cwd().access(host_path, .{}) catch { - return error.PlatformNotSupported; - }; - - // Try to find platform source file (commonly main.roc but could be anything) - const platform_source_path = blk: { - // First try the exact path if it's a .roc file - if (std.mem.endsWith(u8, platform_path, ".roc")) { - std.fs.cwd().access(platform_path, .{}) catch break :blk null; - break :blk platform_path; - } - - // Try common platform source names in the platform directory - const common_names = [_][]const u8{ "main.roc", "platform.roc", "Platform.roc" }; - for (common_names) |name| { - const source_path = try std.fs.path.join(allocs.arena, &.{ platform_dir, name }); - std.fs.cwd().access(source_path, .{}) catch continue; - break :blk source_path; - } - break :blk null; - }; - - return PlatformPaths{ - .host_lib_path = host_path, - .platform_source_path = platform_source_path, - }; - } - - // Try to resolve platform to a local host library and source - const app_dir = std.fs.path.dirname(roc_file_path) orelse "."; - return resolvePlatformSpecToPaths(allocs, platform_spec, app_dir); - } - } - } - } - } - - return error.NoPlatformFound; + // Use the parser to extract the platform spec + const platform_spec = extractPlatformSpecFromApp(allocs, roc_file_path) catch return error.NoPlatformFound; + const app_dir = std.fs.path.dirname(roc_file_path) orelse "."; + return resolvePlatformSpecToPaths(allocs, platform_spec, app_dir); } -/// Extract platform specification from app file header using simple string parsing -/// -/// TODO use this information from BuildEnv once we have the parser/can/typechcking setup -/// for multiple modules, and we have this information available. This is just a temporary hack -/// for testing now. +/// Extract platform specification from app file header by parsing it properly. fn extractPlatformSpecFromApp(allocs: *Allocators, app_file_path: []const u8) ![]const u8 { // Read the app file const source = std.fs.cwd().readFileAlloc(allocs.gpa, app_file_path, std.math.maxInt(usize)) catch return error.FileNotFound; defer allocs.gpa.free(source); - // Simple string parsing to find platform specification - // Look for pattern: platform "..." or platform ".../..." - var lines = std.mem.splitScalar(u8, source, '\n'); - while (lines.next()) |line| { - const trimmed = std.mem.trim(u8, line, " \t\r\n"); - if (std.mem.startsWith(u8, trimmed, "app ")) { - // Look for pf: platform "..." pattern - if (std.mem.indexOf(u8, trimmed, "pf: platform \"")) |start_idx| { - const after_quote = start_idx + 14; // length of "pf: platform \"" - if (std.mem.indexOfScalarPos(u8, trimmed, after_quote, '"')) |end_idx| { - const platform_path = trimmed[after_quote..end_idx]; - return try allocs.arena.dupe(u8, platform_path); - } - } - // Also try alternative format: platform "..." - if (std.mem.indexOf(u8, trimmed, "platform \"")) |start_idx| { - const quote_start = start_idx + 10; // length of "platform \"" - if (std.mem.indexOfScalarPos(u8, trimmed, quote_start, '"')) |end_idx| { - const platform_path = trimmed[quote_start..end_idx]; - return try allocs.arena.dupe(u8, platform_path); - } - } - } - } + // Extract module name from file path + const basename = std.fs.path.basename(app_file_path); + const module_name = try allocs.arena.dupe(u8, basename); - return error.NotAppFile; + // Create ModuleEnv for parsing + var env = ModuleEnv.init(allocs.gpa, source) catch return error.ParseFailed; + defer env.deinit(); + + env.common.source = source; + env.module_name = module_name; + env.common.calcLineStarts(allocs.gpa) catch return error.ParseFailed; + + // Parse the source + var ast = parse.parse(&env.common, allocs.gpa) catch return error.ParseFailed; + defer ast.deinit(allocs.gpa); + + // Get the file header + const file = ast.store.getFile(); + const header = ast.store.getHeader(file.header); + + // Check if this is an app file + switch (header) { + .app => |a| { + // Get the platform field + const pf = ast.store.getRecordField(a.platform_idx); + const value_expr = pf.value orelse return error.NotAppFile; + + // Extract the string value from the expression + const platform_spec = stringFromExpr(&ast, value_expr) catch return error.NotAppFile; + return try allocs.arena.dupe(u8, platform_spec); + }, + else => return error.NotAppFile, + } +} + +/// Extract a string value from an expression (for platform/package paths). +fn stringFromExpr(ast: *parse.AST, expr_idx: parse.AST.Expr.Idx) ![]const u8 { + const e = ast.store.getExpr(expr_idx); + return switch (e) { + .string => |s| { + // For simple strings, iterate through the parts + for (ast.store.exprSlice(s.parts)) |part_idx| { + const part = ast.store.getExpr(part_idx); + if (part == .string_part) { + // Return the first string part (platform specs are simple strings) + return ast.resolve(part.string_part.token); + } + } + return error.ExpectedString; + }, + else => error.ExpectedString, + }; } /// Resolve a platform specification to both host library and platform source paths diff --git a/src/cli/test_shared_memory_system.zig b/src/cli/test_shared_memory_system.zig index 603c922a3d..5d4cdda654 100644 --- a/src/cli/test_shared_memory_system.zig +++ b/src/cli/test_shared_memory_system.zig @@ -90,7 +90,7 @@ test "platform resolution - insecure HTTP URL rejected" { defer temp_dir.cleanup(); const roc_content = - \\app "test" packages { pf: platform "http://example.com/abc123.tar.zst" } imports [pf.Task] provides [main] to pf + \\app [main] { pf: platform "http://example.com/abc123.tar.zst" } \\ \\main = "Hello, World!" ;