Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/pony-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: pony-lint

on:
pull_request:
paths:
- '**/*.pony'

concurrency:
group: pony-lint-${{ github.ref }}
cancel-in-progress: true

permissions:
packages: read

jobs:
pony-lint:
name: Lint Pony source
runs-on: ubuntu-latest
container:
image: ghcr.io/ponylang/shared-docker-ci-standard-builder:release
steps:
- uses: actions/checkout@v6.0.2
- name: Lint
run: make lint
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ GET_DEPENDENCIES_WITH := corral fetch
CLEAN_DEPENDENCIES_WITH := corral clean
COMPILE_WITH := corral run -- ponyc
BUILD_DOCS_WITH := corral run -- pony-doc
LINT_WITH := pony-lint

BUILD_DIR ?= build/$(config)
SRC_DIR := $(PACKAGE)
Expand Down Expand Up @@ -47,6 +48,9 @@ $(EXAMPLES_BINARIES): $(BUILD_DIR)/%: $(SOURCE_FILES) $(EXAMPLES_SOURCE_FILES) |
$(GET_DEPENDENCIES_WITH)
$(PONYC) -o $(BUILD_DIR) $(EXAMPLES_DIR)/$*

lint:
$(LINT_WITH) $(SRC_DIR) $(EXAMPLES_DIR)

clean:
$(CLEAN_DEPENDENCIES_WITH)
rm -rf $(BUILD_DIR)
Expand All @@ -66,4 +70,4 @@ all: test
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)

.PHONY: all build-examples clean TAGS test test-one
.PHONY: all build-examples clean lint TAGS test test-one
26 changes: 21 additions & 5 deletions examples/process-array/process-array.pony
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@ use "runtime_info"
actor Main
new create(env: Env) =>
let array: Array[U8] iso = recover iso [ 201;202;3;4;5;6;7;8;9;10 ] end
let job = fj.Job[Array[U8] iso, USize](
WorkerBuilder,
Generator(consume array),
AddingCollector(env.out),
SchedulerInfoAuth(env.root))
let job =
fj.Job[Array[U8] iso, USize](
WorkerBuilder,
Generator(consume array),
AddingCollector(env.out),
SchedulerInfoAuth(env.root))
job.start()

class WorkerBuilder is fj.WorkerBuilder[Array[U8] iso, USize]
"""
Creates `Adder` worker instances.
"""
fun ref apply(): fj.Worker[Array[U8] iso, USize] iso^ =>
Adder

class Generator is fj.Generator[Array[U8] iso]
"""
Splits an array into chunks distributed across workers.
"""
var _working_set: Array[U8] iso
var _distribution_set: Array[USize] = _distribution_set.create()

Expand All @@ -28,6 +35,9 @@ class Generator is fj.Generator[Array[U8] iso]
_distribution_set = fj.EvenlySplitDataElements(_working_set.size(), workers)

fun ref apply(): Array[U8] iso^ ? =>
"""
Return the next batch of elements, or error when none remain.
"""
if _working_set.size() == 0 then
error
end
Expand All @@ -37,6 +47,9 @@ class Generator is fj.Generator[Array[U8] iso]
consume batch

class AddingCollector is fj.Collector[Array[U8] iso, USize]
"""
Sums worker results and prints the total.
"""
var _total: USize = 0
let _out: OutStream

Expand All @@ -52,6 +65,9 @@ class AddingCollector is fj.Collector[Array[U8] iso, USize]
_out.print(_total.string())

class Adder is fj.Worker[Array[U8] iso, USize]
"""
Sums the elements of a `U8` array.
"""
var _working_set: Array[U8] = _working_set.create()

fun ref receive(work_set: Array[U8] iso) =>
Expand Down
6 changes: 6 additions & 0 deletions examples/process-array/process_array.pony
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
Demonstrates distributing array processing across multiple workers.

Each worker sums its portion of the array, and a collector totals
the partial results.
"""
24 changes: 18 additions & 6 deletions examples/random-number-to-string/random-number-to-string.pony
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@ use "runtime_info"

actor Main
new create(env: Env) =>
let job = fj.Job[USize, String](
WorkerBuilder,
Generator,
StringCollector(env.out),
SchedulerInfoAuth(env.root))
let job =
fj.Job[USize, String](
WorkerBuilder,
Generator,
StringCollector(env.out),
SchedulerInfoAuth(env.root))
job.start()

class WorkerBuilder is fj.WorkerBuilder[USize, String]
"""
Creates USizeToString workers.
"""
fun ref apply(): fj.Worker[USize, String] iso^ =>
USizeToString

class Generator is fj.Generator[USize]
"""
Produces random numbers, ending when one is divisible by 1000.
"""
let _rand: Rand = _rand.create()

fun ref init(workers: USize) =>
Expand All @@ -31,6 +38,9 @@ class Generator is fj.Generator[USize]
x

class StringCollector is fj.Collector[USize, String]
"""
Collects string results and prints them when the job finishes.
"""
let _strings: Array[String] = _strings.create()
let _out: OutStream

Expand All @@ -48,11 +58,13 @@ class StringCollector is fj.Collector[USize, String]
end

class USizeToString is fj.Worker[USize, String]
"""
Converts a USize to its string representation.
"""
var _usize: USize = 0

fun ref receive(work_set: USize) =>
_usize = work_set

fun ref process(runner: fj.WorkerRunner[USize, String] ref) =>
runner.deliver(_usize.string())

3 changes: 3 additions & 0 deletions examples/random-number-to-string/random_number_to_string.pony
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Demonstrates fork_join by converting random numbers to strings in parallel.
"""
60 changes: 41 additions & 19 deletions examples/word-count/word-count.pony
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ type WordCounts is Map[String, USize]
actor Main
new create(env: Env) =>
try
let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
let fp = FilePath(FileAuth(env.root), env.args(1)?, caps)
let caps =
recover val FileCaps .> set(FileRead) .> set(FileStat) end
let fp =
FilePath(FileAuth(env.root), env.args(1)?, caps)
let file = recover iso OpenFile(fp) as File end

let job = fj.Job[String, WordCounts iso](
WorkerBuilder,
FileReader(consume file),
WordCountTotaler(env.out),
SchedulerInfoAuth(env.root))
let job =
fj.Job[String, WordCounts iso](
WorkerBuilder,
FileReader(consume file),
WordCountTotaler(env.out),
SchedulerInfoAuth(env.root))

job.start()
else
Expand All @@ -27,10 +30,16 @@ actor Main
end

class WorkerBuilder is fj.WorkerBuilder[String, WordCounts iso]
"""
Creates SplitAndCount workers.
"""
fun ref apply(): fj.Worker[String, WordCounts iso] iso^ =>
SplitAndCount

class SplitAndCount is fj.Worker[String, WordCounts iso]
"""
Splits input text into words and counts their frequencies.
"""
var _working_set: String = ""

fun ref receive(data: String) =>
Expand All @@ -39,22 +48,33 @@ class SplitAndCount is fj.Worker[String, WordCounts iso]
fun ref process(
runner: fj.WorkerRunner[String, WordCounts iso] ref)
=>
let punctuation = """ !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~‘“ """
"""
Split input into words, count each, and deliver the result.
"""
let punctuation =
""" !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~'" """
let words_and_counts = recover iso WordCounts end
for line in _working_set.split("\n").values() do
let cleaned =
recover val line.lower().>lstrip(punctuation)
.>rstrip(punctuation) end
recover val
line.lower()
.> lstrip(punctuation)
.> rstrip(punctuation)
end
for word in cleaned.split(punctuation).values() do
words_and_counts.upsert(word,
words_and_counts.upsert(
word,
1,
{(current, provided) => current + provided})
{(current, provided) => current + provided })
end
end

runner.deliver(consume words_and_counts)

class FileReader is fj.Generator[String]
"""
Reads lines from a file, one per call.
"""
let _lines: FileLines
var _workers: USize = 0

Expand All @@ -68,25 +88,28 @@ class FileReader is fj.Generator[String]
_lines.next()?

class WordCountTotaler is fj.Collector[String, WordCounts iso]
"""
Merges per-worker word counts and prints the totals.
"""
var _counts: (WordCounts | None) = None
let _out: OutStream

new iso create(out: OutStream) =>
_out = out

fun ref collect(runner: fj.CollectorRunner[String, WordCounts iso] ref,
fun ref collect(
runner: fj.CollectorRunner[String, WordCounts iso] ref,
result: WordCounts iso)
=>
match \exhaustive\ _counts
| None =>
// We haven't gotten any counts yet, instead of copying the map, let's
// just keep the first one as our base to build upon
_counts = consume result
| let counts: WordCounts =>
for (word, count) in (consume result).pairs() do
counts.upsert(word,
count,
{(current, provided) => current + provided})
counts.upsert(
word,
count,
{(current, provided) => current + provided })
end
end

Expand All @@ -100,4 +123,3 @@ class WordCountTotaler is fj.Collector[String, WordCounts iso]
_out.print(word + ":" + count.string())
end
end

3 changes: 3 additions & 0 deletions examples/word-count/word_count.pony
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Demonstrates fork_join by counting word frequencies across a file in parallel.
"""
19 changes: 11 additions & 8 deletions fork_join/_coordinator.pony
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ actor _Coordinator[Input: Any #send, Output: Any #send]
_generator = consume generator
_collector_runner = CollectorRunner[Input, Output](consume collector, this)

_max_workers = if max_workers > 0 then
max_workers
else
Scheduler.schedulers(auth).usize()
end
_max_workers =
if max_workers > 0 then
max_workers
else
Scheduler.schedulers(auth).usize()
end

_generator.init(_max_workers)

Expand All @@ -49,9 +50,11 @@ actor _Coordinator[Input: Any #send, Output: Any #send]
var mw = _max_workers
while true do
// create worker
let w = WorkerRunner[Input, Output](this,
_collector_runner,
_worker_builder())
let w =
WorkerRunner[Input, Output](
this,
_collector_runner,
_worker_builder())
_workers.set(w)

// request data for the worker
Expand Down
10 changes: 6 additions & 4 deletions fork_join/_fail.pony
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ use @pony_os_stderr[Pointer[U8]]()

primitive _Fail
"""
We hit a place in code that we expected that we should be able to reach.
We hit a place in code that we should never be able to reach.
"""
fun apply(loc: SourceLoc = __loc) =>
@fprintf(
@pony_os_stderr(),
("This should never happen: failure in %s at line %s\n" +
"Please open an issue at https://github.com/ponylang/fork_join/issues/new")
.cstring(),
("This should never happen: failure in %s at line %s\n"
+ "Please open an issue at "
+ "https://github.com/ponylang/fork_join"
+ "/issues/new")
.cstring(),
loc.file().cstring(),
loc.line().string().cstring())
@exit(1)
16 changes: 9 additions & 7 deletions fork_join/_test_collector_terminate.pony
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ class \nodoc\ iso _TestCollectorTerminate is UnitTest
because the generator has no end and will keep producing data forever. If
terminate didn't work then the job should continue running.

This test assumes that `finish` isn't called incorrectly before a job is done. We are reliant on the end-to-end test to provide give us a some
provability of that axiom.
This test assumes that `finish` isn't called incorrectly before a
job is done. We are reliant on the end-to-end test to provide give
us some provability of that axiom.

We don't test the results expected as there's not a deterministic number of
results that will be collected despite our always sending the termination
Expand All @@ -23,11 +24,12 @@ class \nodoc\ iso _TestCollectorTerminate is UnitTest
h.long_test(1_000_000_000)
h.expect_action("collector.finish()")

let job = Job[U8, U8](
_CollectorTerminateBuilder,
_CollectorTerminateGenerator,
_CollectorTerminateCollector(h),
SchedulerInfoAuth(h.env.root))
let job =
Job[U8, U8](
_CollectorTerminateBuilder,
_CollectorTerminateGenerator,
_CollectorTerminateCollector(h),
SchedulerInfoAuth(h.env.root))

job.start()

Expand Down
Loading
Loading