Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
119 changes: 119 additions & 0 deletions check_example_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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
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
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
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
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
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
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
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?

Copy link
Contributor Author

@ion098 ion098 Sep 16, 2025

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
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]
code_snippet = "#include \"main.h\"\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())
12 changes: 6 additions & 6 deletions include/pros/abstract_motor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,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 +770,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 +786,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 +838,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 +854,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
36 changes: 18 additions & 18 deletions include/pros/adi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class Port {
* }
* \endcode
*/
explicit Port(std::uint8_t adi_port, adi_port_config_e_t type = E_ADI_TYPE_UNDEFINED);
explicit Port(std::uint8_t adi_port, adi_port_config_e_t type = pros::E_ADI_TYPE_UNDEFINED);

/**
* Configures an ADI port on an adi expander to act as a given sensor type.
Expand All @@ -100,13 +100,13 @@ class Port {
* #define EXT_ADI_SMART_PORT 1
*
* void initialize() {
* pros::adi::Port sensor ({EXT_ADI_SMART_PORT, ANALOG_SENSOR_PORT}, E_ADI_ANALOG_IN);
* // Displays the value of E_ADI_ANALOG_IN
* pros::adi::Port sensor ({EXT_ADI_SMART_PORT, ANALOG_SENSOR_PORT}, pros::E_ADI_ANALOG_IN);
* // Displays the value of pros::E_ADI_ANALOG_IN
* std::cout << "Port Type: " << sensor.get_config();
* }
* \endcode
*/
explicit Port(ext_adi_port_pair_t port_pair, adi_port_config_e_t type = E_ADI_TYPE_UNDEFINED);
explicit Port(ext_adi_port_pair_t port_pair, adi_port_config_e_t type = pros::E_ADI_TYPE_UNDEFINED);

/**
* Gets the configuration for the given ADI port.
Expand All @@ -117,8 +117,8 @@ class Port {
* \code
* #define ANALOG_SENSOR_PORT 1
* void initialize() {
* adi_port_set_config(ANALOG_SENSOR_PORT, E_ADI_ANALOG_IN);
* // Displays the value of E_ADI_ANALOG_IN
* adi_port_set_config(ANALOG_SENSOR_PORT, pros::E_ADI_ANALOG_IN);
* // Displays the value of pros::E_ADI_ANALOG_IN
* printf("Port Type: %d\n", adi_port_get_config(ANALOG_SENSOR_PORT));
* }
* \endcode
Expand All @@ -135,7 +135,7 @@ class Port {
* #define ANALOG_SENSOR_PORT 1
*
* void opcontrol() {
* pros::adi::Port sensor (ANALOG_SENSOR_PORT, E_ADI_ANALOG_IN);
* pros::adi::Port sensor (ANALOG_SENSOR_PORT, pros::E_ADI_ANALOG_IN);
* std::cout << "Port Value: " << sensor.get_value();
* }
* \endcode
Expand All @@ -156,7 +156,7 @@ class Port {
* #define ANALOG_SENSOR_PORT 1
*
* void initialize() {
* pros::adi::Port sensor (ANALOG_SENSOR_PORT, E_ADI_DIGITAL_IN);
* pros::adi::Port sensor (ANALOG_SENSOR_PORT, pros::E_ADI_DIGITAL_IN);
* // Do things as a digital sensor
* // Digital is unplugged and an analog is plugged in
* sensor.set_config(E_ADI_ANALOG_IN);
Expand All @@ -182,7 +182,7 @@ class Port {
* #define DIGITAL_SENSOR_PORT 1
*
* void initialize() {
* pros::adi::Port sensor (DIGITAL_SENSOR_PORT, E_ADI_DIGITAL_OUT);
* pros::adi::Port sensor (DIGITAL_SENSOR_PORT, pros::E_ADI_DIGITAL_OUT);
* sensor.set_value(DIGITAL_SENSOR_PORT, HIGH);
* }
* \endcode
Expand Down Expand Up @@ -459,7 +459,7 @@ class AnalogOut : private Port {
* pros::AnalogOut sensor (ANALOG_SENSOR_PORT);
* // Use the sensor
* }
* @endcode
* \endcode
*/
explicit AnalogOut(std::uint8_t adi_port);

Expand Down Expand Up @@ -798,7 +798,7 @@ class Motor : private Port {
* pros::adi::Motor motor (MOTOR_PORT);
* motor.set_value(127); // Go full speed forward
* std::cout << "Commanded Motor Power: " << motor.get_value(); // Will display 127
* delay(1000);
* pros::delay(1000);
* motor.set_value(0); // Stop the motor
* }
* \endcode
Expand Down Expand Up @@ -826,7 +826,7 @@ class Motor : private Port {
* pros::adi::Motor motor ({EXT_ADI_SMART_PORT, ADI_MOTOR_PORT});
* motor.set_value(127); // Go full speed forward
* std::cout << "Commanded Motor Power: " << motor.get_value(); // Will display 127
* delay(1000);
* pros::delay(1000);
* motor.set_value(0); // Stop the motor
* }
* \endcode
Expand All @@ -851,7 +851,7 @@ class Motor : private Port {
* pros::adi::Motor motor (MOTOR_PORT);
* motor.set_value(127); // Go full speed forward
* std::cout << "Commanded Motor Power: " << motor.get_value(); // Will display 127
* delay(1000);
* pros::delay(1000);
* motor.stop(); // Stop the motor
* }
* \endcode
Expand Down Expand Up @@ -880,7 +880,7 @@ class Motor : private Port {
* pros::adi::Motor motor (MOTOR_PORT);
* motor.set_value(127); // Go full speed forward
* std::cout << "Commanded Motor Power: " << motor.get_value(); // Will display 127
* delay(1000);
* pros::delay(1000);
* motor.set_value(0); // Stop the motor
* }
* \endcode
Expand All @@ -904,7 +904,7 @@ class Motor : private Port {
* pros::adi::Motor motor (MOTOR_PORT);
* motor.set_value(127); // Go full speed forward
* std::cout << "Commanded Motor Power: " << motor.get_value(); // Will display 127
* delay(1000);
* pros::delay(1000);
* motor.set_value(0); // Stop the motor
* }
* \endcode
Expand Down Expand Up @@ -999,7 +999,7 @@ class Encoder : private Port {
*
* void opcontrol() {
* pros::adi::Encoder sensor (PORT_TOP, PORT_BOTTOM, false);
* delay(1000); // Move the encoder around in this time
* pros::delay(1000); // Move the encoder around in this time
* sensor.reset(); // The encoder is now zero again
* }
* \endcode
Expand Down Expand Up @@ -1342,7 +1342,7 @@ class Potentiometer : public AnalogIn {
* }
* \endcode
*/
explicit Potentiometer(std::uint8_t adi_port, adi_potentiometer_type_e_t potentiometer_type = E_ADI_POT_EDR);
explicit Potentiometer(std::uint8_t adi_port, adi_potentiometer_type_e_t potentiometer_type = pros::E_ADI_POT_EDR);

/**
* Configures an ADI port on an adi_expander to act as a Potentiometer.
Expand Down Expand Up @@ -1373,7 +1373,7 @@ class Potentiometer : public AnalogIn {
* }
* \endcode
*/
explicit Potentiometer(ext_adi_port_pair_t port_pair, adi_potentiometer_type_e_t potentiometer_type = E_ADI_POT_EDR);
explicit Potentiometer(ext_adi_port_pair_t port_pair, adi_potentiometer_type_e_t potentiometer_type = pros::E_ADI_POT_EDR);

/**
* Gets the current potentiometer angle in tenths of a degree.
Expand Down
8 changes: 4 additions & 4 deletions include/pros/device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class Device {
* while (true) {
* Device device(DEVICE_PORT);
* printf("device plugged type: {port: %d}\n", device.get_port());
* delay(20);
* pros::delay(20);
* }
* }
* \endcode
Expand All @@ -106,7 +106,7 @@ class Device {
* Device device(DEVICE_PORT);
* while (true) {
* printf("device plugged type: {is_installed: %d}\n", device.is_installed());
* delay(20);
* pros::delay(20);
* }
* }
* \endcode
Expand All @@ -131,7 +131,7 @@ class Device {
* while (true) {
* DeviceType dt = device.get_plugged_type();
* printf("device plugged type: {plugged type: %d}\n", dt);
* delay(20);
* pros::delay(20);
* }
* }
* \endcode
Expand All @@ -158,7 +158,7 @@ class Device {
* while (true) {
* DeviceType dt = pros::Device::get_plugged_type(DEVICE_PORT);
* printf("device plugged type: {plugged type: %d}\n", dt);
* delay(20);
* pros::delay(20);
* }
* }
* \endcode
Expand Down
Loading
Loading