tiny.pluck.toplevel.source
Defined in toplevel.
API (21)
Actions
Public operations.
findFormEndisConstructorNameisIdentifierloadFileloadFileWithWriterlocationFromOffsetslocationFromSpanoffsetToLineColprebindSexprDefsprocessSourceprocessSourceSexprNoWriterprocessSourceSexprWithCallbackprocessSourceSexprWithWriterprocessSourceWithCallbackprocessSourceWithWriterrollbackPreboundDefsskipWhitespaceAndCommentsspanFromOffsets
Types and contracts
Public types and contracts.
Source
Source: lib/pluck/src/toplevel/root.zig:15
zig
pub const source = @import("source.zig");Source: lib/pluck/src/toplevel/source.zig
zig
const std = @import("std");const sys = @import("sys");const pluck = @import("../root.zig");const Allocator = std.mem.Allocator;const pexpr = pluck.pexpr;const PExpr = pexpr.PExpr;const Symbol = pexpr.Symbol;const context_owner = @import("context.zig");const ToplevelContext = context_owner.ToplevelContext;const top_types = @import("types.zig");const ToplevelError = top_types.ToplevelError;const SourceLocation = top_types.SourceLocation;const SourceSpan = top_types.SourceSpan;const result_owner = @import("result.zig");const QueryResult = result_owner.QueryResult;const forms_owner = @import("forms.zig");pub fn loadFile(self: *ToplevelContext, path: []const u8) ToplevelError!void { const source = sys.fs.readFileAlloc(self.allocator, path, 10 * 1024 * 1024) catch |err| switch (err) { error.FileNotFound => return ToplevelError.FileNotFound, else => return ToplevelError.IoError, }; defer self.allocator.free(source); try processSource(self, source);}pub fn loadFileWithWriter(self: *ToplevelContext, path: []const u8, writer: anytype) ToplevelError!void { const source = sys.fs.readFileAlloc(self.allocator, path, 10 * 1024 * 1024) catch |err| switch (err) { error.FileNotFound => return ToplevelError.FileNotFound, else => return ToplevelError.IoError, }; defer self.allocator.free(source); try processSourceWithWriter(self, source, writer);}pub fn processSource(self: *ToplevelContext, source: []const u8) ToplevelError!void { try processSourceSexprNoWriter(self, source);}pub fn processSourceSexprNoWriter(self: *ToplevelContext, source: []const u8) ToplevelError!void { const prebound_defs = try prebindSexprDefs(self, source); defer if (prebound_defs.len > 0) self.allocator.free(prebound_defs); errdefer rollbackPreboundDefs(self, prebound_defs); var pos: usize = 0; self.last_error_span = null; while (pos < source.len) { pos = skipWhitespaceAndComments(source, pos); if (pos >= source.len) break; const form_start = pos; const form_end = findFormEnd(source, pos) orelse { self.last_error_span = spanFromOffsets(source, form_start, source.len); return ToplevelError.ParseError; }; pos = form_end; const form_source = source[form_start..form_end]; if (form_source.len == 0) continue; const result = forms_owner.processFormSexpr(self, form_source) catch |err| { self.last_error_span = spanFromOffsets(source, form_start, form_end); return err; }; if (result) |*query_result| { @constCast(query_result).deinit(); } }}pub fn processSourceWithWriter(self: *ToplevelContext, source: []const u8, writer: anytype) ToplevelError!void { try processSourceSexprWithWriter(self, source, writer);}pub const QueryResultCallback = *const fn (*QueryResult, *anyopaque) void;pub fn processSourceWithCallback( self: *ToplevelContext, source: []const u8, callback: QueryResultCallback, context: *anyopaque,) ToplevelError!void { try processSourceSexprWithCallback(self, source, callback, context);}pub fn processSourceSexprWithWriter(self: *ToplevelContext, source: []const u8, writer: anytype) ToplevelError!void { const prebound_defs = try prebindSexprDefs(self, source); defer if (prebound_defs.len > 0) self.allocator.free(prebound_defs); errdefer rollbackPreboundDefs(self, prebound_defs); var pos: usize = 0; self.last_error_span = null; while (pos < source.len) { pos = skipWhitespaceAndComments(source, pos); if (pos >= source.len) break; const form_start = pos; const form_end = findFormEnd(source, pos) orelse { self.last_error_span = spanFromOffsets(source, form_start, source.len); return ToplevelError.ParseError; }; pos = form_end; const form_source = source[form_start..form_end]; if (form_source.len == 0) continue; const result = forms_owner.processFormSexpr(self, form_source) catch |err| { self.last_error_span = spanFromOffsets(source, form_start, form_end); return err; }; if (result) |*query_result| { defer @constCast(query_result).deinit(); query_result.print(writer) catch {}; writer.flush() catch {}; } }}pub fn processSourceSexprWithCallback( self: *ToplevelContext, source: []const u8, callback: QueryResultCallback, context: *anyopaque,) ToplevelError!void { const prebound_defs = try prebindSexprDefs(self, source); defer if (prebound_defs.len > 0) self.allocator.free(prebound_defs); errdefer rollbackPreboundDefs(self, prebound_defs); var pos: usize = 0; self.last_error_span = null; while (pos < source.len) { pos = skipWhitespaceAndComments(source, pos); if (pos >= source.len) break; const form_start = pos; const form_end = findFormEnd(source, pos) orelse { self.last_error_span = spanFromOffsets(source, form_start, source.len); return ToplevelError.ParseError; }; pos = form_end; const form_source = source[form_start..form_end]; if (form_source.len == 0) continue; const source_loc = locationFromOffsets(source, form_start, form_end); const result = forms_owner.processFormSexpr(self, form_source) catch |err| { self.last_error_span = spanFromOffsets(source, form_start, form_end); return err; }; if (result) |*query_result| { var mutable_result = query_result.*; mutable_result.source_location = source_loc; callback(&mutable_result, context); } }}pub const PreboundDef = struct { name: Symbol, prev_expr: ?*PExpr, prev_is_stdlib: bool, prev_doc: ?[]const u8,};pub fn prebindSexprDefs(self: *ToplevelContext, source: []const u8) ToplevelError![]PreboundDef { const arena_alloc = self.arena.allocator(); var prebound: std.ArrayListUnmanaged(PreboundDef) = .empty; errdefer prebound.deinit(self.allocator); var seen: std.StringHashMapUnmanaged(void) = .{}; defer seen.deinit(self.allocator); var pos: usize = 0; while (pos < source.len) { pos = skipWhitespaceAndComments(source, pos); if (pos >= source.len) break; const form_start = pos; const form_end = findFormEnd(source, pos) orelse return ToplevelError.ParseError; pos = form_end; const form_source = source[form_start..form_end]; if (form_source.len == 0) continue; const tokens = pexpr.tokenize(self.allocator, form_source) catch return ToplevelError.OutOfMemory; defer pexpr.freeTokens(self.allocator, tokens); if (extractSexprDefName(tokens)) |name| { try prebindDefinition(self, arena_alloc, name, &prebound, &seen); } } return prebound.toOwnedSlice(self.allocator) catch return ToplevelError.OutOfMemory;}fn extractSexprDefName(tokens: []const pexpr.Token) ?[]const u8 { if (tokens.len < 4) return null; if (!std.mem.eql(u8, tokens[0], "(")) return null; if (!std.mem.eql(u8, tokens[1], "define")) return null; if (std.mem.eql(u8, tokens[2], "(")) { if (tokens.len >= 4 and isIdentifier(tokens[3])) { return tokens[3]; } return null; } if (isIdentifier(tokens[2])) { return tokens[2]; } return null;}fn prebindDefinition( self: *ToplevelContext, arena_alloc: Allocator, name: []const u8, prebound: *std.ArrayListUnmanaged(PreboundDef), seen: *std.StringHashMapUnmanaged(void),) ToplevelError!void { if (seen.contains(name)) return; const duped_name = arena_alloc.dupe(u8, name) catch return ToplevelError.OutOfMemory; try seen.put(self.allocator, duped_name, {}); const prev_def = self.definitions.lookupDefinition(duped_name); const prev_expr = if (prev_def) |d| d.expr else null; const prev_is_stdlib = if (prev_def) |d| d.is_stdlib else false; const prev_doc = if (prev_def) |d| d.doc else null; _ = self.definitions.remove(duped_name); const dummy = PExpr.init(arena_alloc, .{ .construct = .{ .constructor = "Unit" } }) catch return ToplevelError.OutOfMemory; self.definitions.define(duped_name, dummy) catch return ToplevelError.OutOfMemory; prebound.append(self.allocator, .{ .name = duped_name, .prev_expr = prev_expr, .prev_is_stdlib = prev_is_stdlib, .prev_doc = prev_doc, }) catch return ToplevelError.OutOfMemory;}pub fn rollbackPreboundDefs(self: *ToplevelContext, prebound: []const PreboundDef) void { for (prebound) |entry| { _ = self.definitions.remove(entry.name); if (entry.prev_expr) |expr| { if (entry.prev_is_stdlib) { if (entry.prev_doc) |doc| { self.definitions.defineStdlibWithDoc(entry.name, expr, doc) catch {}; } else { self.definitions.defineStdlib(entry.name, expr) catch {}; } } else { if (entry.prev_doc) |doc| { self.definitions.defineWithDoc(entry.name, expr, doc) catch {}; } else { self.definitions.define(entry.name, expr) catch {}; } } } }}pub const LineCol = struct { line: u32, col: u32,};pub fn spanFromOffsets(source: []const u8, start: usize, end: usize) SourceSpan { const start_loc = offsetToLineCol(source, start); const end_loc = offsetToLineCol(source, end); return .{ .start = .{ .line = start_loc.line, .column = start_loc.col, .offset = @min(start, source.len), }, .end = .{ .line = end_loc.line, .column = end_loc.col, .offset = @min(end, source.len), }, };}pub fn locationFromSpan(span: SourceSpan) SourceLocation { return .{ .start_line = span.start.line, .start_col = span.start.column, .end_line = span.end.line, .end_col = span.end.column, };}pub fn locationFromOffsets(source: []const u8, start: usize, end: usize) SourceLocation { return locationFromSpan(spanFromOffsets(source, start, end));}pub fn offsetToLineCol(source: []const u8, offset: usize) LineCol { var line: u32 = 0; var col: u32 = 0; for (source[0..@min(offset, source.len)]) |c| { if (c == '\n') { line += 1; col = 0; } else { col += 1; } } return .{ .line = line, .col = col };}pub fn isIdentifier(token: []const u8) bool { if (token.len == 0) return false; const first = token[0]; if (!std.ascii.isAlphabetic(first) and first != '_' and !isOperatorIdentifierStart(first)) return false; for (token[1..]) |c| { if (!std.ascii.isAlphanumeric(c) and c != '_' and c != '-' and c != '?' and c != '=' and !isOperatorIdentifierStart(c)) return false; } return true;}fn isOperatorIdentifierStart(c: u8) bool { return switch (c) { '+', '-', '*', '/', '<', '>', '=' => true, else => false, };}pub fn isConstructorName(token: []const u8) bool { if (token.len == 0) return false; const first = token[0]; if (!std.ascii.isAlphabetic(first) and first != '_') return false; for (token[1..]) |c| { if (!std.ascii.isAlphanumeric(c) and c != '_' and c != '-') return false; } return true;}test "constructor names accept Pluck.jl reference spellings" { try std.testing.expect(isConstructorName("a_")); try std.testing.expect(isConstructorName("the_")); try std.testing.expect(isConstructorName("Start")); try std.testing.expect(isConstructorName("non-terminal")); try std.testing.expect(!isConstructorName("+")); try std.testing.expect(!isConstructorName("nat=?")); try std.testing.expect(!isConstructorName(""));}test "processSource records absolute form span on form error" { var ctx = try ToplevelContext.init(std.testing.allocator); defer ctx.deinit(); const source_text = \\(Marginal True) \\(define p ) ; try std.testing.expectError(ToplevelError.InvalidDefineForm, ctx.processSource(source_text)); const span = ctx.lastErrorSpan().?; try std.testing.expectEqual(@as(u32, 1), span.start.line); try std.testing.expectEqual(@as(usize, 16), span.start.offset);}pub fn skipWhitespaceAndComments(source: []const u8, start: usize) usize { var pos = start; while (pos < source.len) { const c = source[pos]; if (std.ascii.isWhitespace(c)) { pos += 1; continue; } if (pos + 1 < source.len and source[pos] == ';' and source[pos + 1] == ';') { while (pos < source.len and source[pos] != '\n') { pos += 1; } continue; } break; } return pos;}pub fn findFormEnd(source: []const u8, start: usize) ?usize { if (start >= source.len) return null; if (source[start] != '(') { var pos = start; while (pos < source.len and !std.ascii.isWhitespace(source[pos]) and source[pos] != '(') { pos += 1; } return pos; } var depth: i32 = 0; var pos = start; var in_string = false; while (pos < source.len) { const c = source[pos]; if (c == '"' and (pos == 0 or source[pos - 1] != '\\')) { in_string = !in_string; pos += 1; continue; } if (in_string) { pos += 1; continue; } if (pos + 1 < source.len and c == ';' and source[pos + 1] == ';') { while (pos < source.len and source[pos] != '\n') { pos += 1; } continue; } if (c == '(') { depth += 1; } else if (c == ')') { depth -= 1; if (depth == 0) { return pos + 1; } } pos += 1; } return null;}Audit
| Definitions | 22 |
|---|---|
| Public names | 33 |
| Members | 6 |
| Version | 26.7.0 |
| Revision | daab053ee433 |