Skip to content

Commit 8da9b1e

Browse files
committed
refactor(common/utils): simplify context implementation
1 parent af1ce71 commit 8da9b1e

File tree

9 files changed

+89
-124
lines changed

9 files changed

+89
-124
lines changed

eslint.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ export default defineConfig(
5151
{
5252
name: 'exuanbo/react',
5353
basePath: 'src',
54-
ignores: ['core/**'],
5554
extends: [
5655
{
5756
name: 'react/jsx',

src/common/utils/context.ts

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,52 @@
22
// https://blog.skk.moe/post/context-in-javascript/
33
// CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
44

5-
export type ApplyProvider = <R>(callback: () => R) => R
6-
7-
export interface ContextProvider<T> {
8-
<R>(props: { value: T, callback: () => R }): R
9-
(props: { value: T }): ApplyProvider
10-
}
11-
12-
export interface ContextConsumer<T> {
13-
<R>(callback: (value: T) => R): R
14-
(): T
15-
}
5+
import { invariant } from './invariant'
166

177
export interface Context<T> {
18-
Provider: ContextProvider<T>
19-
Consumer: ContextConsumer<T>
8+
get(): T
9+
run<R>(value: T, fn: () => R): R
10+
run(value: T): <R>(fn: () => R) => R
2011
}
2112

22-
const NO_VALUE_DEFAULT = Symbol('NO_VALUE_DEFAULT')
23-
type ContextValue<T> = T | typeof NO_VALUE_DEFAULT
13+
const NIL = Symbol('NIL')
14+
15+
export function createContext<T>(defaultValue: T | typeof NIL = NIL): Context<T> {
16+
let value = defaultValue
2417

25-
export function createContext<T>(defaultValue: ContextValue<T> = NO_VALUE_DEFAULT): Context<T> {
26-
let contextValue = defaultValue
18+
function get() {
19+
invariant(value != NIL)
20+
return value
21+
}
2722

28-
const Provider = <R>({ value, callback }: { value: T, callback?: () => R }) => {
29-
if (!callback) {
30-
return (fn: typeof callback) => Provider({ value, callback: fn })
23+
function run<R>(next: T, cb?: () => R) {
24+
if (!cb) {
25+
return (fn: typeof cb) => run(next, fn)
3126
}
32-
const currentValue = contextValue
33-
contextValue = value
27+
const prev = value
28+
value = next
3429
try {
35-
return callback()
30+
return cb()
3631
}
3732
finally {
38-
contextValue = currentValue
33+
value = prev
3934
}
4035
}
4136

42-
const Consumer = <R>(callback?: (value: T) => R) => {
43-
if (contextValue === NO_VALUE_DEFAULT) {
44-
throw new TypeError('Missing context: use within Provider or set default value.')
45-
}
46-
if (!callback) {
47-
return contextValue
48-
}
49-
return callback(contextValue)
50-
}
51-
5237
return {
53-
Provider,
54-
Consumer,
38+
get,
39+
run,
5540
}
5641
}
5742

58-
export function useContext<T>(Context: Context<T>): T {
59-
return Context.Consumer()
60-
}
43+
export type Executor = <R>(fn: () => R) => R
6144

62-
export interface ContextComposeProviderProps<R> {
63-
contexts: ApplyProvider[]
64-
callback: () => R
65-
}
45+
export function compose<R>(executors: Executor[], fn: () => R): R
6646

67-
export function ComposeProvider<R>({ contexts, callback }: ContextComposeProviderProps<R>): R {
68-
const applyProviders = contexts.reduceRight(
47+
export function compose<R>(executors: Executor[], cb: () => R) {
48+
const execute = executors.reduceRight(
6949
(composed, current) => (fn) => current(() => composed(fn)),
7050
(fn) => fn(),
7151
)
72-
return applyProviders(callback)
52+
return execute(cb)
7353
}

src/core/assembler/assembler.state.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContext, useContext } from '@/common/utils/context'
1+
import { createContext } from '@/common/utils/context'
22

33
import { getSize } from './assembler.utils'
44
import type { AssemblyNode } from './assemblyunit'
@@ -36,7 +36,3 @@ export function createAssemblerState(initialAddress = 0): AssemblerState {
3636
}
3737

3838
export const AssemblerState = createContext<AssemblerState>()
39-
40-
export function useAssemblerState(): AssemblerState {
41-
return useContext(AssemblerState)
42-
}

src/core/assembler/assembler.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { expectNever } from 'ts-expect'
22

3-
import { ComposeProvider } from '@/common/utils/context'
3+
import { compose } from '@/common/utils/context'
44
import { invariant } from '@/common/utils/invariant'
55

6-
import { AssemblerState, createAssemblerState, useAssemblerState } from './assembler.state'
6+
import { AssemblerState, createAssemblerState } from './assembler.state'
77
import { getSize, hasIdentifier, type WithIdentifier } from './assembler.utils'
88
import {
99
type AssemblyNode,
@@ -18,7 +18,7 @@ import * as InstrSet from './instrset'
1818
import { resolveOpcode } from './instrset.utils'
1919
import { createLexer } from './lexer'
2020
import { createParser } from './parser'
21-
import { createParserContext, ParserContext, useParserContext } from './parser.context'
21+
import { createParserContext, ParserContext } from './parser.context'
2222
import { createTokenStream, TokenStream } from './token.stream'
2323

2424
interface PendingChunk extends CodeChunk {
@@ -44,26 +44,27 @@ export class Assembler {
4444
assemble(input: string): AssemblyUnit {
4545
const lexer = createLexer(input)
4646
const stream = createTokenStream(lexer)
47+
4748
const context = createParserContext()
4849
const initialState = createAssemblerState()
49-
return ComposeProvider({
50-
contexts: [
51-
AssemblerState.Provider({ value: initialState }),
52-
ParserContext.Provider({ value: context }),
53-
TokenStream.Provider({ value: stream }),
50+
return compose(
51+
[
52+
AssemblerState.run(initialState),
53+
ParserContext.run(context),
54+
TokenStream.run(stream),
5455
],
55-
callback: () => {
56+
() => {
5657
const unit = this.setup()
5758
const ast = this.parse()
5859
return this.hasError()
5960
? finalize(unit, { chunks: [] })
6061
: finalize(unit, { ast, labels: context.labels })
6162
},
62-
})
63+
)
6364
}
6465

6566
private parse(): AST.Program | null {
66-
const context = useParserContext()
67+
const context = ParserContext.get()
6768
const parser = createParser()
6869
try {
6970
while (true) {
@@ -125,7 +126,7 @@ export class Assembler {
125126
}
126127

127128
private updateAddress(node: AST.Immediate | ProcessableNode): void {
128-
const state = useAssemblerState()
129+
const state = AssemblerState.get()
129130
if (node.type === AST.NodeType.Immediate) {
130131
this.catchError(() => state.setAddress(node))
131132
}
@@ -158,15 +159,15 @@ export class Assembler {
158159
}
159160

160161
private processLabel({ children: [name] }: AST.Identifier): void {
161-
const state = useAssemblerState()
162-
const context = useParserContext()
162+
const state = AssemblerState.get()
163+
const context = ParserContext.get()
163164
const label = context.labels.get(name)
164165
invariant(label?.loc, `Label '${name}' is undefined`)
165166
label.address = state.address
166167
}
167168

168169
private processInstruction(node: AST.Instruction): void {
169-
const state = useAssemblerState()
170+
const state = AssemblerState.get()
170171
const buffer = new Uint8Array(getSize(node))
171172
const baseChunk = {
172173
offset: state.address,
@@ -185,7 +186,7 @@ export class Assembler {
185186
}
186187

187188
private processDataByte(node: AST.Db): void {
188-
const state = useAssemblerState()
189+
const state = AssemblerState.get()
189190
const buffer = new Uint8Array(this.resolve(node.children))
190191
this.unit.chunks.push({
191192
offset: state.address,
@@ -197,10 +198,7 @@ export class Assembler {
197198
private processPendingChunks(): void {
198199
this.pendings.forEach((chunk) => {
199200
const state = createAssemblerState(chunk.offset)
200-
AssemblerState.Provider({
201-
value: state,
202-
callback: () => this.encodePending(chunk),
203-
})
201+
AssemblerState.run(state, () => this.encodePending(chunk))
204202
})
205203
}
206204

@@ -243,8 +241,8 @@ function unsafe_resolve(nodes: ResolvableNode[]): number[] {
243241
}
244242

245243
function resolveIdentifier({ children: [name], loc }: AST.Identifier): number {
246-
const state = useAssemblerState()
247-
const context = useParserContext()
244+
const state = AssemblerState.get()
245+
const context = ParserContext.get()
248246
const label = context.labels.get(name)
249247
invariant(label, `Label '${name}' is not added`)
250248
invariant(!Number.isNaN(label.address), `Label '${name}' is not processed`)

src/core/assembler/parser.context.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContext, useContext } from '@/common/utils/context'
1+
import { createContext } from '@/common/utils/context'
22

33
import type * as AST from './ast'
44
import { ErrorCode, type ParserDiagnostic, ParserError, ParserWarning } from './errors'
@@ -80,7 +80,3 @@ export function createParserContext(): ParserContext {
8080
}
8181

8282
export const ParserContext = createContext<ParserContext>()
83-
84-
export function useParserContext(): ParserContext {
85-
return useContext(ParserContext)
86-
}

src/core/assembler/parser.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from 'vitest'
22

3-
import { ComposeProvider } from '@/common/utils/context'
3+
import { compose } from '@/common/utils/context'
44
import { examples } from '@/features/editor/examples'
55

66
import * as AST from './ast'
@@ -33,13 +33,13 @@ function applyParser<Node extends AST.Node>(
3333
) {
3434
const lexer = createLexer(input)
3535
const stream = createTokenStream(lexer)
36-
return ComposeProvider({
37-
contexts: [
38-
ParserContext.Provider({ value: context }),
39-
TokenStream.Provider({ value: stream }),
36+
return compose(
37+
[
38+
ParserContext.run(context),
39+
TokenStream.run(stream),
4040
],
41-
callback: parse,
42-
})
41+
parse,
42+
)
4343
}
4444

4545
describe('Parser', () => {

0 commit comments

Comments
 (0)