You are browsing as a guest. Sign up (or log in) to start making projects!

samuel_0xk

@samuel_0xk

Joined June 2nd, 2026

  • 3Devlogs
  • 1Projects
  • 0Ships
  • 0Votes
Hi there đź‘‹

check out github: github.com/Samuelk0nrad
Open comments for this post

1h 19m 32s logged

finally I made it v0.5.0 is finish!!

v0.5.0 is released

• Simpler context.Builder API
• Turn-aware history with summarization
• Richer OpenTelemetry tracing + debug events
• Cleaner agent and model APIs
• Better token accounting
• Legacy prompt, RAG, and session abstractions removed

https://github.com/lace-ai/gai

finally I made it v0.5.0 is finish!!

v0.5.0 is released

• Simpler context.Builder API
• Turn-aware history with summarization
• Richer OpenTelemetry tracing + debug events
• Cleaner agent and model APIs
• Better token accounting
• Legacy prompt, RAG, and session abstractions removed

https://github.com/lace-ai/gai

Replying to @samuel_0xk

0
5
Open comments for this post

4h 4m 46s logged

What I updated:

  • The history source to include summaries
  • summary agent
  • Prompt Renderer

The history source now summarieses the past conversation if it doesn’t fit into the token budget.

You can configure it like this:

history, err := history.New(sessionID, store, &history.SummarizerDefinition{
	Model:   model,
	Enabled: true,
})

or create a custom summarizer and pass it in the SummarizerDefinition


The Summary agent got some bug fixes and better error handling

It doesn’t panic if no model is configured :)


The Renderer and the whole Prompt builder is updated with the focus on better renderer final prompts

  • there is now a Render Part function that turns a part into a RenderNode
  • Part and RenderNode can be recursive / hierarchical

the default XML Renderer renders something like this:

<system>...</system>
<history>
    <user>...</user>
    <assistant>...</assistant>
    <user>...</user>
    ....
</history>
....

What I updated:

  • The history source to include summaries
  • summary agent
  • Prompt Renderer

The history source now summarieses the past conversation if it doesn’t fit into the token budget.

You can configure it like this:

history, err := history.New(sessionID, store, &history.SummarizerDefinition{
	Model:   model,
	Enabled: true,
})

or create a custom summarizer and pass it in the SummarizerDefinition


The Summary agent got some bug fixes and better error handling

It doesn’t panic if no model is configured :)


The Renderer and the whole Prompt builder is updated with the focus on better renderer final prompts

  • there is now a Render Part function that turns a part into a RenderNode
  • Part and RenderNode can be recursive / hierarchical

the default XML Renderer renders something like this:

<system>...</system>
<history>
    <user>...</user>
    <assistant>...</assistant>
    <user>...</user>
    ....
</history>
....

Replying to @samuel_0xk

0
2
Open comments for this post
Reposted by @samuel_0xk

8h 33m 56s logged

Hi, everyone đź‘‹

This is the Devlog of what changed in gai v0.5.0

I have updated the prompt builder and the budget handling for gai, the last couple of days

Before there was a AI generated, huge and complex builder full of bugs and problems.

Now gai has an intuitive simple and flexible to use prompt builder with build in budget managing

The whole builder is build on the concept of an stack that is separated in three parts:

  • System Instructions
  • Context
  • User Prompt / Current Loop

System Instructions:

These are just static parts that could be just text but also something else, doesn’t matter.

There is a helper for loading instructions from a file, that turns these directly into a part that can be used as system instructions.

systemInstructions, err := gaictx.LoadPromptFromFile(pathToFile)

Context

Context is special, it is used for dynamic sources, like history. These sources get rendered at the start of the loop and are just functions that return a part.

A source is just an interface:

type ContextSource interface {
	Name() string
	Function(ctx context.Context, TokenBudget int) (Part, error)
}

Currently there is just the history source, but in the future I will add some more, but they are meant for the user to implement them self.

Current Loop

The last section is just the user prompt and the last assistant massages or tool calls (if the assistant calls tools)
These are rendered per Iterations.
Nothing more.

Token Budget

The token budget is pretty simple, you pass in a token budget, and reserved output tokens. and the BuildContext function iterates over the sources and passes in the remaining token budget.

So Yes, if the first source needs all the budget, the last sources don’t have any budget left.


The internal flow is like this:

First you pass in the system instructions, context sources, and user prompt:

gaictx.New(gaictx.Definition{
	SystemInstructions: []gaictx.Part{
		systemInstructions,
		toolInstructions,
	},
	ContextSources: []gaictx.ContextSource{
		gaictx.NewHistory(sessionID, store),
	},
	UserPrompt: input.Text,
})

Now the stack looks like this: {[part,part], [source], [part]}

Then one time at the start of the loop the sources are build: {[part,part], [part], [part]}

parts, err := a.PromptBuilder.BuildContext(ctx)

On each iteration, the current iterations parts get added to the end, and the prompt gets rendered (default XML render)
{[part,part], [source], [part, part, part, part]}
=> <system>...</system><history><user>...</user></history><userPrompt>...</userPrompt><assistant>...</assistant><tool>...</tool>....


I also updated the way how to build reusable agents.

here is a complete example:

agent.New(agent.Definition{
		Name:  "user-assistant",
		Model: model,
		Tools: []loop.Tool{webSearch},
		Prompt: func(ctx context.Context, input agent.RunInput) (gaictx.PromptBuilder, error) {
			return prompt := gaictx.New(gaictx.Definition{
				SystemInstructions: []gaictx.Part{
					systemInstructions,
					toolInstructions,
				},
				ContextSources: []gaictx.ContextSource{
					gaictx.NewHistory(sessionID, store),
				},
				UserPrompt: input.Text,
			}), nil
		},
	})

Work that needs to be done until I publish the new version

  • the history needs to use the summarizer
  • some small bug fixes
  • summery should be more dynamic
    if it summaries, the summary of turns 1-3 and turns 4-6, this should be somewhere saved, and persisted.
  • maybe some more issues that I find while using the library

if someone is rely curios here is the pr: https://github.com/lace-ai/gai/pull/28

Hi, everyone đź‘‹

This is the Devlog of what changed in gai v0.5.0

I have updated the prompt builder and the budget handling for gai, the last couple of days

Before there was a AI generated, huge and complex builder full of bugs and problems.

Now gai has an intuitive simple and flexible to use prompt builder with build in budget managing

The whole builder is build on the concept of an stack that is separated in three parts:

  • System Instructions
  • Context
  • User Prompt / Current Loop

System Instructions:

These are just static parts that could be just text but also something else, doesn’t matter.

There is a helper for loading instructions from a file, that turns these directly into a part that can be used as system instructions.

systemInstructions, err := gaictx.LoadPromptFromFile(pathToFile)

Context

Context is special, it is used for dynamic sources, like history. These sources get rendered at the start of the loop and are just functions that return a part.

A source is just an interface:

type ContextSource interface {
	Name() string
	Function(ctx context.Context, TokenBudget int) (Part, error)
}

Currently there is just the history source, but in the future I will add some more, but they are meant for the user to implement them self.

Current Loop

The last section is just the user prompt and the last assistant massages or tool calls (if the assistant calls tools)
These are rendered per Iterations.
Nothing more.

Token Budget

The token budget is pretty simple, you pass in a token budget, and reserved output tokens. and the BuildContext function iterates over the sources and passes in the remaining token budget.

So Yes, if the first source needs all the budget, the last sources don’t have any budget left.


The internal flow is like this:

First you pass in the system instructions, context sources, and user prompt:

gaictx.New(gaictx.Definition{
	SystemInstructions: []gaictx.Part{
		systemInstructions,
		toolInstructions,
	},
	ContextSources: []gaictx.ContextSource{
		gaictx.NewHistory(sessionID, store),
	},
	UserPrompt: input.Text,
})

Now the stack looks like this: {[part,part], [source], [part]}

Then one time at the start of the loop the sources are build: {[part,part], [part], [part]}

parts, err := a.PromptBuilder.BuildContext(ctx)

On each iteration, the current iterations parts get added to the end, and the prompt gets rendered (default XML render)
{[part,part], [source], [part, part, part, part]}
=> <system>...</system><history><user>...</user></history><userPrompt>...</userPrompt><assistant>...</assistant><tool>...</tool>....


I also updated the way how to build reusable agents.

here is a complete example:

agent.New(agent.Definition{
		Name:  "user-assistant",
		Model: model,
		Tools: []loop.Tool{webSearch},
		Prompt: func(ctx context.Context, input agent.RunInput) (gaictx.PromptBuilder, error) {
			return prompt := gaictx.New(gaictx.Definition{
				SystemInstructions: []gaictx.Part{
					systemInstructions,
					toolInstructions,
				},
				ContextSources: []gaictx.ContextSource{
					gaictx.NewHistory(sessionID, store),
				},
				UserPrompt: input.Text,
			}), nil
		},
	})

Work that needs to be done until I publish the new version

  • the history needs to use the summarizer
  • some small bug fixes
  • summery should be more dynamic
    if it summaries, the summary of turns 1-3 and turns 4-6, this should be somewhere saved, and persisted.
  • maybe some more issues that I find while using the library

if someone is rely curios here is the pr: https://github.com/lace-ai/gai/pull/28

Replying to @samuel_0xk

1
16
Open comments for this post

8h 33m 56s logged

Hi, everyone đź‘‹

This is the Devlog of what changed in gai v0.5.0

I have updated the prompt builder and the budget handling for gai, the last couple of days

Before there was a AI generated, huge and complex builder full of bugs and problems.

Now gai has an intuitive simple and flexible to use prompt builder with build in budget managing

The whole builder is build on the concept of an stack that is separated in three parts:

  • System Instructions
  • Context
  • User Prompt / Current Loop

System Instructions:

These are just static parts that could be just text but also something else, doesn’t matter.

There is a helper for loading instructions from a file, that turns these directly into a part that can be used as system instructions.

systemInstructions, err := gaictx.LoadPromptFromFile(pathToFile)

Context

Context is special, it is used for dynamic sources, like history. These sources get rendered at the start of the loop and are just functions that return a part.

A source is just an interface:

type ContextSource interface {
	Name() string
	Function(ctx context.Context, TokenBudget int) (Part, error)
}

Currently there is just the history source, but in the future I will add some more, but they are meant for the user to implement them self.

Current Loop

The last section is just the user prompt and the last assistant massages or tool calls (if the assistant calls tools)
These are rendered per Iterations.
Nothing more.

Token Budget

The token budget is pretty simple, you pass in a token budget, and reserved output tokens. and the BuildContext function iterates over the sources and passes in the remaining token budget.

So Yes, if the first source needs all the budget, the last sources don’t have any budget left.


The internal flow is like this:

First you pass in the system instructions, context sources, and user prompt:

gaictx.New(gaictx.Definition{
	SystemInstructions: []gaictx.Part{
		systemInstructions,
		toolInstructions,
	},
	ContextSources: []gaictx.ContextSource{
		gaictx.NewHistory(sessionID, store),
	},
	UserPrompt: input.Text,
})

Now the stack looks like this: {[part,part], [source], [part]}

Then one time at the start of the loop the sources are build: {[part,part], [part], [part]}

parts, err := a.PromptBuilder.BuildContext(ctx)

On each iteration, the current iterations parts get added to the end, and the prompt gets rendered (default XML render)
{[part,part], [source], [part, part, part, part]}
=> <system>...</system><history><user>...</user></history><userPrompt>...</userPrompt><assistant>...</assistant><tool>...</tool>....


I also updated the way how to build reusable agents.

here is a complete example:

agent.New(agent.Definition{
		Name:  "user-assistant",
		Model: model,
		Tools: []loop.Tool{webSearch},
		Prompt: func(ctx context.Context, input agent.RunInput) (gaictx.PromptBuilder, error) {
			return prompt := gaictx.New(gaictx.Definition{
				SystemInstructions: []gaictx.Part{
					systemInstructions,
					toolInstructions,
				},
				ContextSources: []gaictx.ContextSource{
					gaictx.NewHistory(sessionID, store),
				},
				UserPrompt: input.Text,
			}), nil
		},
	})

Work that needs to be done until I publish the new version

  • the history needs to use the summarizer
  • some small bug fixes
  • summery should be more dynamic
    if it summaries, the summary of turns 1-3 and turns 4-6, this should be somewhere saved, and persisted.
  • maybe some more issues that I find while using the library

if someone is rely curios here is the pr: https://github.com/lace-ai/gai/pull/28

Hi, everyone đź‘‹

This is the Devlog of what changed in gai v0.5.0

I have updated the prompt builder and the budget handling for gai, the last couple of days

Before there was a AI generated, huge and complex builder full of bugs and problems.

Now gai has an intuitive simple and flexible to use prompt builder with build in budget managing

The whole builder is build on the concept of an stack that is separated in three parts:

  • System Instructions
  • Context
  • User Prompt / Current Loop

System Instructions:

These are just static parts that could be just text but also something else, doesn’t matter.

There is a helper for loading instructions from a file, that turns these directly into a part that can be used as system instructions.

systemInstructions, err := gaictx.LoadPromptFromFile(pathToFile)

Context

Context is special, it is used for dynamic sources, like history. These sources get rendered at the start of the loop and are just functions that return a part.

A source is just an interface:

type ContextSource interface {
	Name() string
	Function(ctx context.Context, TokenBudget int) (Part, error)
}

Currently there is just the history source, but in the future I will add some more, but they are meant for the user to implement them self.

Current Loop

The last section is just the user prompt and the last assistant massages or tool calls (if the assistant calls tools)
These are rendered per Iterations.
Nothing more.

Token Budget

The token budget is pretty simple, you pass in a token budget, and reserved output tokens. and the BuildContext function iterates over the sources and passes in the remaining token budget.

So Yes, if the first source needs all the budget, the last sources don’t have any budget left.


The internal flow is like this:

First you pass in the system instructions, context sources, and user prompt:

gaictx.New(gaictx.Definition{
	SystemInstructions: []gaictx.Part{
		systemInstructions,
		toolInstructions,
	},
	ContextSources: []gaictx.ContextSource{
		gaictx.NewHistory(sessionID, store),
	},
	UserPrompt: input.Text,
})

Now the stack looks like this: {[part,part], [source], [part]}

Then one time at the start of the loop the sources are build: {[part,part], [part], [part]}

parts, err := a.PromptBuilder.BuildContext(ctx)

On each iteration, the current iterations parts get added to the end, and the prompt gets rendered (default XML render)
{[part,part], [source], [part, part, part, part]}
=> <system>...</system><history><user>...</user></history><userPrompt>...</userPrompt><assistant>...</assistant><tool>...</tool>....


I also updated the way how to build reusable agents.

here is a complete example:

agent.New(agent.Definition{
		Name:  "user-assistant",
		Model: model,
		Tools: []loop.Tool{webSearch},
		Prompt: func(ctx context.Context, input agent.RunInput) (gaictx.PromptBuilder, error) {
			return prompt := gaictx.New(gaictx.Definition{
				SystemInstructions: []gaictx.Part{
					systemInstructions,
					toolInstructions,
				},
				ContextSources: []gaictx.ContextSource{
					gaictx.NewHistory(sessionID, store),
				},
				UserPrompt: input.Text,
			}), nil
		},
	})

Work that needs to be done until I publish the new version

  • the history needs to use the summarizer
  • some small bug fixes
  • summery should be more dynamic
    if it summaries, the summary of turns 1-3 and turns 4-6, this should be somewhere saved, and persisted.
  • maybe some more issues that I find while using the library

if someone is rely curios here is the pr: https://github.com/lace-ai/gai/pull/28

Replying to @samuel_0xk

1
16
Open comments for this post
Reposted by @samuel_0xk

4h 34m 56s logged

updateee!!! ( sorry was busyy lol )


Last time: kill the fake stuff, make the dashboard real.

This week: make it actually run your startup / project.


talk to your startup

The orchestrator is the main control surface now.

made a literal jarvis u can talk to do anything literally - pull up stats , spawn 67 sub agents literally anything

First load? It greets you with where your workspace is at. No blank screen.

Text chat still lives in the right panel if you’d rather type quietly.


the graph moves

11 nodes still the backbone. But you can now spawn custom research nodes on the fly.

“Dig into competitor gaps” → parallel researchers spin up, new nodes drop on the canvas, wired to the right parent. Or hit + on the canvas and kick off a custom task yourself.

Left rail shows the agent tree with live status dots. Animated edges when something’s running.


pivot without starting over

Ideas change. A diff classifier figures out what’s actually affected — only those nodes reset and re-research.

Affected nodes blur on the canvas. You don’t lose everything because you tweaked positioning.


observe → grow → act

  • Build observer — GitHub commits update the build node
  • Observe agent — PostHog funnels, flags conversion drops
  • Growth agent — ranks the highest-ROI next move

Shows up as today’s priority in the action bar. Mark done or hand it to the orchestrator in one click.


shippable output

Revenue + product + tech all at 70%+ → approve & export unlocks.

Scaffold zip: README, tech stack, UI spec, .cursorrules, handoff doc. Preview in modal, download, drop into Cursor. – literally how clajude design now works like btw


decision journal

Every graph mutation logged — what changed, why, evidence, confidence shifts.

Scroll the history, click through to the node. Useful when you’re three pivots deep.


under the hood

  • Karpathy-style research loop with critique scoring (accepts at 80+) – best one out there
  • Google ADK planner breaks workspace into research tasks
  • MongoDB MCP wired into the agent loop at runtime
  • Firecrawl, Reddit, Scrapling for market evidence (mite add more )
  • SSE streams agent progress + graph updates live
  • Node history tab — full attempt log per node

polish

Confidence rings, unlock animations, startup health score, branded landing page, workspace resume on reload.


not yet

OAuth (still PAT), real multi-project, Gmail/Slack, persona lens, auth.


next

OAuth → multi-project → Gmail/Slack → deeper observe/growth loops.

see ya on next one byeee :)

updateee!!! ( sorry was busyy lol )


Last time: kill the fake stuff, make the dashboard real.

This week: make it actually run your startup / project.


talk to your startup

The orchestrator is the main control surface now.

made a literal jarvis u can talk to do anything literally - pull up stats , spawn 67 sub agents literally anything

First load? It greets you with where your workspace is at. No blank screen.

Text chat still lives in the right panel if you’d rather type quietly.


the graph moves

11 nodes still the backbone. But you can now spawn custom research nodes on the fly.

“Dig into competitor gaps” → parallel researchers spin up, new nodes drop on the canvas, wired to the right parent. Or hit + on the canvas and kick off a custom task yourself.

Left rail shows the agent tree with live status dots. Animated edges when something’s running.


pivot without starting over

Ideas change. A diff classifier figures out what’s actually affected — only those nodes reset and re-research.

Affected nodes blur on the canvas. You don’t lose everything because you tweaked positioning.


observe → grow → act

  • Build observer — GitHub commits update the build node
  • Observe agent — PostHog funnels, flags conversion drops
  • Growth agent — ranks the highest-ROI next move

Shows up as today’s priority in the action bar. Mark done or hand it to the orchestrator in one click.


shippable output

Revenue + product + tech all at 70%+ → approve & export unlocks.

Scaffold zip: README, tech stack, UI spec, .cursorrules, handoff doc. Preview in modal, download, drop into Cursor. – literally how clajude design now works like btw


decision journal

Every graph mutation logged — what changed, why, evidence, confidence shifts.

Scroll the history, click through to the node. Useful when you’re three pivots deep.


under the hood

  • Karpathy-style research loop with critique scoring (accepts at 80+) – best one out there
  • Google ADK planner breaks workspace into research tasks
  • MongoDB MCP wired into the agent loop at runtime
  • Firecrawl, Reddit, Scrapling for market evidence (mite add more )
  • SSE streams agent progress + graph updates live
  • Node history tab — full attempt log per node

polish

Confidence rings, unlock animations, startup health score, branded landing page, workspace resume on reload.


not yet

OAuth (still PAT), real multi-project, Gmail/Slack, persona lens, auth.


next

OAuth → multi-project → Gmail/Slack → deeper observe/growth loops.

see ya on next one byeee :)

Replying to @hariharann

2
283
Open comments for this post
Reposted by @samuel_0xk

3h 13m 29s logged

devlog#6

  • added a guide app
  • and 3 new live wallpapers with parallax effect
  • restyled dock and app icons a bit
  • new songs and covers
  • feature to resize window’s length and breadth

Go check it out!! :3 its done now (hopefully, i dont want more bugs 💔✌)

devlog#6

  • added a guide app
  • and 3 new live wallpapers with parallax effect
  • restyled dock and app icons a bit
  • new songs and covers
  • feature to resize window’s length and breadth

Go check it out!! :3 its done now (hopefully, i dont want more bugs 💔✌)

Replying to @subhansh

6
5541
Open comments for this post
Reposted by @samuel_0xk

3h 38m 25s logged

Devlog: Replacing Flower’s Old Function Syntax with func name(...): type

This feature started out as what I thought would be a straightforward syntax cleanup.

Flower’s old function syntax looked like this:

int add(a: int, b: int):
    return a + b
end

and exported functions looked like this:

prop int test():
    return 1
end

It worked, technically, but it had started to annoy me more. The biggest issue was that function declarations did not visually match the direction Flower was already heading in, and instead felt much more C-like than I wanted.

prop func create(...): Person
func main(): int

This style says “this is a function” up front, gives a clean place for modifiers like prop, and keeps the return type attached to the signature rather than awkwardly leading it.

The goal was simple:

func name(...): type

instead of:

type name(...):

Simple in theory. not so much when the compiler is written in the language being refactored.

The Problem

Flower’s parser assumes top-level function definitions start with a type. Old parser logic was built around that:

int main():

means:

  1. parse a type,
  2. an identifier,
  3. params,
  4. and a body

That assumption was scattered through a few different places:

  • normal function definitions
  • prop function declarations
  • forward declarations
  • examples and docs
  • the compiler’s own source

Now, in retrospect, this was actually one of the easier changes. It didn’t take a whole lot of planning nor brain power, and I was able to change it pretty quickly. Luckily for me, I’ve built codegen to rely purely on ast values rather than shaping.

The New Shape

The new syntax is:

func add(a: int, b: int): int
    return a + b
end

and exported functions now look like:

prop func test(): int
    return 1
end

This is nicer because:

  • func makes declarations immediately recognizable
  • prop composes more cleanly with func
  • return types now live where people expect them to
  • future syntax has more room to grow without feeling backwards

It also feels more in line with Flower’s broader goals: explicit, readable, and not complex.

The Refactor

Previously, parse_func_def looked for a return type first:

type name(...):

Now it expects:

func name(...): type

So the order changed to:

  1. expect func
  2. parse function name
  3. parse parameter list
  4. expect :
  5. parse return type
  6. parse body until end

That part was conceptually easy.

The more annoying part was updating all the places that assumed if it isn’t a var decl and it looks like a func, it’s a func.

That fallback had to go (Good riddance!).

Instead, top-level parsing needed to become more explicit:

  • prop func → exported function
  • func → normal function
  • forward func → forward declaration

The old one worked, but it relied on vague “anything else is probably a function” rules.

The Bootstrap Mistake

At one point, I made a bootstrap build so I could use a Flower_new binary to compile the refactored compiler. Unfortunately, I had made a mistake in parse_forward: advancing before peek checks.

Feel free to check out the bootstrapper to see why this was an issue ;)

This meant my “new compiler to compile the new compiler” was built from a broken parser.

The workaround was cursed, but it worked:

  1. check out the commit before the refactor
  2. fix the bug
  3. compile a new binary
  4. switch to the refactor branch
  5. use the binary to compile the new version

Since the compiled binary was ignored by Git, this was actually fairly simple.

Not the most elegant bootstrap story, but it got the compiler to, well, compile.

Devlog: Replacing Flower’s Old Function Syntax with func name(...): type

This feature started out as what I thought would be a straightforward syntax cleanup.

Flower’s old function syntax looked like this:

int add(a: int, b: int):
    return a + b
end

and exported functions looked like this:

prop int test():
    return 1
end

It worked, technically, but it had started to annoy me more. The biggest issue was that function declarations did not visually match the direction Flower was already heading in, and instead felt much more C-like than I wanted.

prop func create(...): Person
func main(): int

This style says “this is a function” up front, gives a clean place for modifiers like prop, and keeps the return type attached to the signature rather than awkwardly leading it.

The goal was simple:

func name(...): type

instead of:

type name(...):

Simple in theory. not so much when the compiler is written in the language being refactored.

The Problem

Flower’s parser assumes top-level function definitions start with a type. Old parser logic was built around that:

int main():

means:

  1. parse a type,
  2. an identifier,
  3. params,
  4. and a body

That assumption was scattered through a few different places:

  • normal function definitions
  • prop function declarations
  • forward declarations
  • examples and docs
  • the compiler’s own source

Now, in retrospect, this was actually one of the easier changes. It didn’t take a whole lot of planning nor brain power, and I was able to change it pretty quickly. Luckily for me, I’ve built codegen to rely purely on ast values rather than shaping.

The New Shape

The new syntax is:

func add(a: int, b: int): int
    return a + b
end

and exported functions now look like:

prop func test(): int
    return 1
end

This is nicer because:

  • func makes declarations immediately recognizable
  • prop composes more cleanly with func
  • return types now live where people expect them to
  • future syntax has more room to grow without feeling backwards

It also feels more in line with Flower’s broader goals: explicit, readable, and not complex.

The Refactor

Previously, parse_func_def looked for a return type first:

type name(...):

Now it expects:

func name(...): type

So the order changed to:

  1. expect func
  2. parse function name
  3. parse parameter list
  4. expect :
  5. parse return type
  6. parse body until end

That part was conceptually easy.

The more annoying part was updating all the places that assumed if it isn’t a var decl and it looks like a func, it’s a func.

That fallback had to go (Good riddance!).

Instead, top-level parsing needed to become more explicit:

  • prop func → exported function
  • func → normal function
  • forward func → forward declaration

The old one worked, but it relied on vague “anything else is probably a function” rules.

The Bootstrap Mistake

At one point, I made a bootstrap build so I could use a Flower_new binary to compile the refactored compiler. Unfortunately, I had made a mistake in parse_forward: advancing before peek checks.

Feel free to check out the bootstrapper to see why this was an issue ;)

This meant my “new compiler to compile the new compiler” was built from a broken parser.

The workaround was cursed, but it worked:

  1. check out the commit before the refactor
  2. fix the bug
  3. compile a new binary
  4. switch to the refactor branch
  5. use the binary to compile the new version

Since the compiled binary was ignored by Git, this was actually fairly simple.

Not the most elegant bootstrap story, but it got the compiler to, well, compile.

Replying to @IvyMycelia

2
411

Followers

Loading…