Skip to content
Open
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
2 changes: 2 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
onlyAddExtraIndex: true
- bash: pip install pros-cli
displayName: Install CLI
- bash: python check_example_code.py
displayName: Check example code in headers
- bash: |
make template
mkdir -p artifacts
Expand Down
120 changes: 120 additions & 0 deletions check_example_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import asyncio
import os
import re
import subprocess
import sys
import tempfile

include_directory = "include/pros"
precompiled_include_directory = ""
error_count = 0
gcc_semaphore = asyncio.Semaphore(value=os.cpu_count())

def print_and_exit(message):
print(f"Failed to check example code in pros headers:\n{message}", file=sys.stderr)
sys.exit(1)

def precompile_header():
flags = [
"-x", "c++-header",
"include/main.h",
"-std=c++23",
"-I", "include",
"-D", "_PROS_KERNEL_SUPPRESS_LLEMU_WARNING",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huge nitpick (like, dont bother changing this) but for the record its UB to have any identifier (including macros) that start with underscore then a capital letter lol.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually idek if you wrote that macro or not but 🤷 just having fun being an asshole by mentioning it

"-Wfatal-errors",
"-o", os.path.join(precompiled_include_directory, "main.h.gch")
]
subprocess.run(["arm-none-eabi-gcc", *flags], check=True)

async def compile_code(code_text, filename, is_cpp):
"""
Compiles the given code, discarding the output file.

Args:
code_text (str): The C/C++ source code to compile.
is_cpp (bool): Whether the code is C++ or not.

Returns:
success (bool): Whether the compilation succeeded or not.
"""
flags = [
"-x", "c++" if is_cpp else "c",
"-std=c++23" if is_cpp else "-std=c2x",
# Only need precompiled headers for C++
*(("-I", precompiled_include_directory) if is_cpp else ()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird syntax trick lol maybe just do it after the list initialization

"-I", "include",
# Prevent spurious warnings
"-D", "_PROS_KERNEL_SUPPRESS_LLEMU_WARNING",
# Stop compilation on first error
"-Wfatal-errors",
# Read input from stdin
"-",
# Generate assembly (avoids linker errors)
"-S",
Comment on lines +52 to +53

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the more standard -c not work? if not then probably give a longer comment to explain why youre doing -S because it took me a second for that to click

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-c would probably work, but assembling the files seems like an unnecessary step.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, maybe just a better comment - something like Just generate assembly, the further compilation steps are not necessary for this CI check maybe

# Discard the output file
"-o", os.devnull
]
async with gcc_semaphore:
process = await asyncio.create_subprocess_exec(
"arm-none-eabi-gcc",
*flags,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await process.communicate(input=code_text.encode("utf-8"))
success = (process.returncode == 0)
Comment on lines +65 to +66

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting - where do you await the compilation being done? im not super familiar with this but im confused. does process.communicate() imply blocking to wait for a response? is there a timeout?

@ion098 ion098 Sep 16, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, communicate sends input via stdin and then waits for the process to finish running (no timeout though)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok maybe a comment that says that because its not obvious imo


if not success:
global error_count
error_count += 1
print(f"=== example code from {filename} failed to compile ===")
print(code_text)
print("=== compiler output below: ===")
print(stderr.decode())
print("========")

return success

def example_code_generator():
try:
files = os.listdir(include_directory)
except:
print_and_exit(f"Error accessing directory '{include_directory}': {e}")
try:
for filename in files:
file_path = os.path.join(include_directory, filename)
if os.path.isfile(file_path):
try:
with open(file_path, "r") as f:
header_text = f.read()
is_cpp = filename.endswith(".hpp")
# This pattern matches all lines between the \code and \endcode markers
pattern = r"(^.+\\code\n)((.*\n)+?)(^.+\\endcode)"
for match in re.finditer(pattern, header_text, re.MULTILINE):
code_block = match.group(2)
lines = code_block.splitlines()
# Remove the leading * from each line
cleaned_lines = [re.sub(r"^\s*\* ?", "", line) for line in lines]
include_apix = "#include \"pros/apix.h\"" if filename == "apix.h" else ""
code_snippet = f"#include \"main.h\"\n{include_apix}\n"+"\n".join(cleaned_lines)
yield code_snippet, filename, is_cpp
except Exception as e:
print_and_exit(f"Error reading file '{filename}': {e}")
except Exception as e:
print_and_exit(f"Error accessing directory '{include_directory}': {e}")

async def main():
with tempfile.TemporaryDirectory() as tmpdirname:
global precompiled_include_directory
precompiled_include_directory = tmpdirname
precompile_header()
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(compile_code(*item)) for item in example_code_generator()]
results = [task.result() for task in tasks]
global error_count
if error_count > 0:
print_and_exit(f"{error_count} compile errors encountered")

if __name__ == "__main__":
asyncio.run(main())
2 changes: 2 additions & 0 deletions include/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "pros/rotation.h"
#include "pros/rtos.h"
#include "pros/screen.h"
#include "pros/serial.h"
#include "pros/vision.h"

#ifdef __cplusplus
Expand All @@ -75,6 +76,7 @@
#include "pros/rotation.hpp"
#include "pros/rtos.hpp"
#include "pros/screen.hpp"
#include "pros/serial.hpp"
#include "pros/vision.hpp"
#endif

Expand Down
23 changes: 17 additions & 6 deletions include/pros/abstract_motor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ enum class MotorBrake {
invalid = INT32_MAX ///< Invalid brake mode
};

// TODO: implement
std::ostream& operator<<(std::ostream& os, MotorBrake brake_mode);

/**
* \enum MotorEncoderUnits
* Indicates the units used by the motor encoders.
Expand All @@ -53,6 +56,9 @@ enum class MotorEncoderUnits {
invalid = INT32_MAX ///< Invalid motor encoder units
};

// TODO: implement
std::ostream& operator<<(std::ostream& os, MotorEncoderUnits brake_mode);

// Alias for MotorEncoderUnits
using MotorUnits = MotorEncoderUnits;

Expand All @@ -69,6 +75,9 @@ enum class MotorGears {
invalid = INT32_MAX ///< Error return code
};

// TODO: implement
std::ostream& operator<<(std::ostream& os, MotorGears brake_mode);

/**
* \enum MotorType
* Indicates the type of a motor
Expand All @@ -79,6 +88,8 @@ enum class MotorType {
invalid = INT32_MAX ///< Error return code
};

// TODO: implement
std::ostream& operator<<(std::ostream& os, MotorType brake_mode);

// Provide Aliases for MotorGears
using MotorGearset = MotorGears;
Expand Down Expand Up @@ -172,8 +183,8 @@ class AbstractMotor {
*
* This velocity corresponds to different actual speeds depending on the
* gearset used for the motor. This results in a range of +-100 for
* E_MOTOR_GEARSET_36, +-200 for E_MOTOR_GEARSET_18, and +-600 for
* E_MOTOR_GEARSET_6. The velocity is held with PID to ensure consistent
* pros::E_MOTOR_GEARSET_36, +-200 for pros::E_MOTOR_GEARSET_18, and +-600 for
* pros::E_MOTOR_GEARSET_6. The velocity is held with PID to ensure consistent
* speed, as opposed to setting the motor's voltage.
*
* This function uses the following values of errno when an error state is
Expand Down Expand Up @@ -770,7 +781,7 @@ class AbstractMotor {
* By default index is 0, and will return an error for an out of bounds index
*
* \return One of MotorBrake, according to what was set for the
* motor, or E_MOTOR_BRAKE_INVALID if the operation failed, setting errno.
* motor, or pros::E_MOTOR_BRAKE_INVALID if the operation failed, setting errno.
*/
virtual MotorBrake get_brake_mode(const std::uint8_t index = 0) const = 0;

Expand All @@ -786,7 +797,7 @@ class AbstractMotor {
* By default index is 0, and will return an error for an out of bounds index
*
* \return A vector containing MotorBrake(s), according to what was set for the
* motor(s), or E_MOTOR_BRAKE_INVALID if the operation failed, setting errno.
* motor(s), or pros::E_MOTOR_BRAKE_INVALID if the operation failed, setting errno.
*/
virtual std::vector<MotorBrake> get_brake_mode_all(void) const = 0;

Expand Down Expand Up @@ -838,7 +849,7 @@ class AbstractMotor {
* By default index is 0, and will return an error for an out of bounds index
*
* \return One of MotorUnits according to what is set for the
* motor or E_MOTOR_ENCODER_INVALID if the operation failed.
* motor or pros::E_MOTOR_ENCODER_INVALID if the operation failed.
*/
virtual MotorUnits get_encoder_units(const std::uint8_t index = 0) const = 0;

Expand All @@ -854,7 +865,7 @@ class AbstractMotor {
* By default index is 0, and will return an error for an out of bounds index
*
* \return A vector of MotorUnits according to what is set for the
* motor(s) or E_MOTOR_ENCODER_INVALID if the operation failed.
* motor(s) or pros::E_MOTOR_ENCODER_INVALID if the operation failed.
*/
virtual std::vector<MotorUnits> get_encoder_units_all(void) const = 0;

Expand Down
12 changes: 6 additions & 6 deletions include/pros/adi.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ adi_port_config_e_t adi_port_get_config(uint8_t port);
*
* void opcontrol() {
* adi_port_set_config(ANALOG_SENSOR_PORT, E_ADI_ANALOG_IN);
* printf("Port Value: %d\n", adi_get_value(ANALOG_SENSOR_PORT));
* printf("Port Value: %d\n", adi_port_get_value(ANALOG_SENSOR_PORT));
* }
* \endcode
*/
Expand Down Expand Up @@ -253,7 +253,7 @@ int32_t adi_port_set_config(uint8_t port, adi_port_config_e_t type);
*
* void initialize() {
* adi_port_set_config(DIGITAL_SENSOR_PORT, E_ADI_DIGITAL_OUT);
* adi_set_value(DIGITAL_SENSOR_PORT, HIGH);
* adi_port_set_value(DIGITAL_SENSOR_PORT, HIGH);
* }
* \endcode
*/
Expand Down Expand Up @@ -495,7 +495,7 @@ int32_t adi_digital_get_new_press(uint8_t port);
*
* \b Example
* \code
* #define DIGITAL_SENSOR_PORT
* #define DIGITAL_SENSOR_PORT 1
*
* void opcontrol() {
* bool state = LOW;
Expand Down Expand Up @@ -1114,9 +1114,9 @@ adi_potentiometer_t adi_potentiometer_type_init(uint8_t port, adi_potentiometer_
* #define POTENTIOMETER_PORT 1
*
* void opcontrol() {
* adi_potentiometer_t potentiometer = adi_potentiometer_t(POTENTIOMETER_PORT);
* adi_potentiometer_t potentiometer = POTENTIOMETER_PORT;
* while (true) {
* // Print the potnetiometer's angle
* // Print the potentiometer's angle
* printf("Angle: %lf\n", adi_potentiometer_get_angle(potentiometer));
* delay(5);
* }
Expand Down Expand Up @@ -1192,7 +1192,7 @@ adi_led_t adi_led_init(uint8_t port);
* delay(5);
*
* // Clear the led strip
* adi_led_clear(led);
* adi_led_clear_all(led, buffer, 10);
* delay(5);
* }
* }
Expand Down
Loading
Loading